mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[jsscripting] Automatically convert event data in UI-based scripts from Java to JS types (#19260)
* [jsscripting] Automatically convert event data in UI-based scripts from Java to JS types Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
@@ -71,16 +71,57 @@ console.log("Thing status",thingStatusInfo.getStatus());
|
||||
|
||||
See [openhab-js](https://openhab.github.io/openhab-js) for a complete list of functionality.
|
||||
|
||||
### UI Event Object
|
||||
### Event Object
|
||||
|
||||
**NOTE**: Note that `event` object is different in UI based rules and file based rules! This section is only valid for UI based rules. If you use file based rules, refer to [file based rules event object documentation](#event-object).
|
||||
Note that `event` object is only available when the UI based rule was triggered by an event and is not called from another rule!
|
||||
Trying to access `event` in this case does not work and will lead to an error. Use `this.event` instead (will be `undefined` when it does not exist).
|
||||
When a rule is triggered, the script is provided the event instance that triggered it.
|
||||
The specific data depends on the event type.
|
||||
The `event` object provides some information about that trigger.
|
||||
|
||||
When you use "Item event" as trigger (i.e. "[item] received a command", "[item] was updated", "[item] changed"), there is additional context available for the action in a variable called `event`.
|
||||
When a rule is triggered, there is additional context available for the action in a variable called `event`.
|
||||
This table gives an overview over the `event` object:
|
||||
|
||||
This table gives an overview over the `event` object for most common trigger types:
|
||||
| Property Name | Trigger Types | Description | Rules DSL Equivalent |
|
||||
|-------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------|------------------------|
|
||||
| `oldState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | Previous state of Item or Group that triggered event | `previousState` |
|
||||
| `newState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | New state of Item or Group that triggered event | N/A |
|
||||
| `receivedState` | `ItemStateUpdateTrigger`, `GroupStateUpdateTrigger` | State of Item that triggered event | `triggeringItem.state` |
|
||||
| `receivedCommand` | `ItemCommandTrigger`, `GroupCommandTrigger` | Command that triggered event | `receivedCommand` |
|
||||
| `itemName` | `Item****Trigger`, `Group****Trigger` | Name of Item that triggered event | `triggeringItem.name` |
|
||||
| `groupName` | `Group****Trigger` | Name of the group whose member triggered event | N/A |
|
||||
| `receivedEvent` | `ChannelEventTrigger` | Channel event that triggered event | N/A |
|
||||
| `channelUID` | `ChannelEventTrigger` | UID of channel that triggered event | N/A |
|
||||
| `oldStatus` | `ThingStatusChangeTrigger` | Previous state of Thing that triggered event | N/A |
|
||||
| `newStatus` | `ThingStatusChangeTrigger` | New state of Thing that triggered event | N/A |
|
||||
| `status` | `ThingStatusUpdateTrigger` | State of Thing that triggered event | N/A |
|
||||
| `thingUID` | `Thing****Trigger` | UID of Thing that triggered event | N/A |
|
||||
| `cronExpression` | `GenericCronTrigger` | Cron expression of the trigger | N/A |
|
||||
| `time` | `TimeOfDayTrigger` | Time of day value of the trigger | N/A |
|
||||
| `timeOnly` | `DateTimeTrigger` | Whether the trigger only considers the time part of the DateTime Item | N/A |
|
||||
| `offset` | `DateTimeTrigger` | Offset in seconds added to the time of the DateTime Item | N/A |
|
||||
| `eventType` | all except `PWMTrigger`, `PIDTrigger` | Type of event that triggered event (change, command, triggered, update, time) | N/A |
|
||||
| `triggerType` | all except `PWMTrigger`, `PIDTrigger` | Type of trigger that triggered event | N/A |
|
||||
| `eventName` | all | simple Java class name of the triggering event, e.g. `ExecutionEvent` | N/A |
|
||||
| `eventClass` | all | full Java class name of the triggering event, e.g. `org.openhab.core.automation.events.ExecutionEvent` | N/A |
|
||||
| `module` | all | (user-defined or auto-generated) name of trigger | N/A |
|
||||
| `raw` | all | Original contents of the event including data passed from a calling rule | N/A |
|
||||
|
||||
All properties are typeof `string` except for properties contained by `raw` which are unmodified from the original types.
|
||||
|
||||
Please note that when using `GenericEventTrigger`, the available properties depend on the chosen event types.
|
||||
It is not possible for the openhab-js library to provide type conversions for all properties of all openHAB events, as those are too many.
|
||||
In case the event object does not provide type-conversed properties for your chosen event type, use the `payload` property to gain access to the event's (Java data type) payload.
|
||||
|
||||
**NOTE:**
|
||||
`Group****Trigger`s use the equivalent `Item****Trigger` as trigger for each member.
|
||||
|
||||
See [openhab-js : EventObject](https://openhab.github.io/openhab-js/rules.html#.EventObject) for full API documentation.
|
||||
|
||||
When disabling the option _Convert Event from Java to JavaScript type in UI-based scripts_, you will receive a raw Java event object instead of the `event` object described above.
|
||||
See the expandable section below for more details.
|
||||
|
||||
<details>
|
||||
<summary>Raw UI Event Object</summary>
|
||||
|
||||
This table gives an overview over the raw Java `event` object for UI-based scripts for most common trigger types:
|
||||
|
||||
| Property Name | Type | Trigger Types | Description | Rules DSL Equivalent |
|
||||
|----------------|----------------------------------------------------------------------------------------------------------------------|----------------------------------------|---------------------------------------------------------------------------------------------------------------|------------------------|
|
||||
@@ -110,6 +151,8 @@ console.log(event.itemState == "test") // WRONG. Will always log "false"
|
||||
console.log(event.itemState.toString() == "test") // OK
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Scripting Basics
|
||||
|
||||
The openHAB JavaScript Scripting runtime attempts to provide a familiar environment to JavaScript developers.
|
||||
@@ -1192,6 +1235,9 @@ Local variable state is not persisted among reloads, see using the [cache](#cach
|
||||
|
||||
File based rules can be created in 2 different ways: using [JSRule](#jsrule) or the [Rule Builder](#rule-builder).
|
||||
|
||||
When a rule is triggered, the script is provided information about the event that triggered the rule in the `event` object.
|
||||
Please refer to [Event Object](#event-object) for documentation.
|
||||
|
||||
See [openhab-js : rules](https://openhab.github.io/openhab-js/rules.html) for full API documentation.
|
||||
|
||||
### JSRule
|
||||
@@ -1412,55 +1458,6 @@ rules.when(true).item('HallLight').receivedCommand().then().sendIt().toItem('Kit
|
||||
rules.when(true).item('HallLight').receivedUpdate().then().copyState().fromItem('BedroomLight1').toItem('BedroomLight2').build();
|
||||
```
|
||||
|
||||
### Event Object
|
||||
|
||||
**NOTE**: The `event` object is different in UI Based Rules and File Based Rules!
|
||||
This section is only valid for File Based Rules.
|
||||
If you use UI Based Rules, refer to [UI based rules event object documentation](#ui-event-object).
|
||||
|
||||
When a rule is triggered, the script is provided the event instance that triggered it.
|
||||
The specific data depends on the event type.
|
||||
The `event` object provides some information about that trigger.
|
||||
|
||||
This table gives an overview over the `event` object:
|
||||
|
||||
| Property Name | Trigger Types | Description | Rules DSL Equivalent |
|
||||
|-------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------|------------------------|
|
||||
| `oldState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | Previous state of Item or Group that triggered event | `previousState` |
|
||||
| `newState` | `ItemStateChangeTrigger`, `GroupStateChangeTrigger` | New state of Item or Group that triggered event | N/A |
|
||||
| `receivedState` | `ItemStateUpdateTrigger`, `GroupStateUpdateTrigger` | State of Item that triggered event | `triggeringItem.state` |
|
||||
| `receivedCommand` | `ItemCommandTrigger`, `GroupCommandTrigger` | Command that triggered event | `receivedCommand` |
|
||||
| `itemName` | `Item****Trigger`, `Group****Trigger` | Name of Item that triggered event | `triggeringItem.name` |
|
||||
| `groupName` | `Group****Trigger` | Name of the group whose member triggered event | N/A |
|
||||
| `receivedEvent` | `ChannelEventTrigger` | Channel event that triggered event | N/A |
|
||||
| `channelUID` | `ChannelEventTrigger` | UID of channel that triggered event | N/A |
|
||||
| `oldStatus` | `ThingStatusChangeTrigger` | Previous state of Thing that triggered event | N/A |
|
||||
| `newStatus` | `ThingStatusChangeTrigger` | New state of Thing that triggered event | N/A |
|
||||
| `status` | `ThingStatusUpdateTrigger` | State of Thing that triggered event | N/A |
|
||||
| `thingUID` | `Thing****Trigger` | UID of Thing that triggered event | N/A |
|
||||
| `cronExpression` | `GenericCronTrigger` | Cron expression of the trigger | N/A |
|
||||
| `time` | `TimeOfDayTrigger` | Time of day value of the trigger | N/A |
|
||||
| `timeOnly` | `DateTimeTrigger` | Whether the trigger only considers the time part of the DateTime Item | N/A |
|
||||
| `offset` | `DateTimeTrigger` | Offset in seconds added to the time of the DateTime Item | N/A |
|
||||
| `eventType` | all except `PWMTrigger`, `PIDTrigger` | Type of event that triggered event (change, command, triggered, update, time) | N/A |
|
||||
| `triggerType` | all except `PWMTrigger`, `PIDTrigger` | Type of trigger that triggered event | N/A |
|
||||
| `eventName` | all | simple Java class name of the triggering event, e.g. `ExecutionEvent` | N/A |
|
||||
| `eventClass` | all | full Java class name of the triggering event, e.g. `org.openhab.core.automation.events.ExecutionEvent` | N/A |
|
||||
| `module` | all | (user-defined or auto-generated) name of trigger | N/A |
|
||||
| `raw` | all | Original contents of the event including data passed from a calling rule | N/A |
|
||||
|
||||
All properties are typeof `string` except for properties contained by `raw` which are unmodified from the original types.
|
||||
|
||||
Please note that when using `GenericEventTrigger`, the available properties depend on the chosen event types.
|
||||
It is not possible for the openhab-js library to provide type conversions for all properties of all openHAB events, as those are too many.
|
||||
In case the event object does not provide type-conversed properties for your chosen event type, use the `payload` property to gain access to the event's (Java data type) payload.
|
||||
|
||||
**NOTE:**
|
||||
`Group****Trigger`s use the equivalent `Item****Trigger` as trigger for each member.
|
||||
Time triggers do not provide any event instance, therefore no property is populated.
|
||||
|
||||
See [openhab-js : EventObject](https://openhab.github.io/openhab-js/rules.html#.EventObject) for full API documentation.
|
||||
|
||||
## Advanced Scripting
|
||||
|
||||
### Libraries
|
||||
|
||||
+25
@@ -31,6 +31,7 @@ public class GraalJSScriptEngineConfiguration {
|
||||
private static final String CFG_INJECTION_ENABLED = "injectionEnabledV2";
|
||||
private static final String CFG_INJECTION_CACHING_ENABLED = "injectionCachingEnabled";
|
||||
private static final String CFG_WRAPPER_ENABLED = "wrapperEnabled";
|
||||
private static final String CFG_EVENT_CONVERSION_ENABLED = "eventConversionEnabled";
|
||||
private static final String CFG_DEPENDENCY_TRACKING_ENABLED = "dependencyTrackingEnabled";
|
||||
|
||||
private static final int INJECTION_ENABLED_FOR_UI_BASED_SCRIPTS_ONLY = 1;
|
||||
@@ -40,6 +41,7 @@ public class GraalJSScriptEngineConfiguration {
|
||||
private int injectionEnabled = INJECTION_ENABLED_FOR_ALL_SCRIPTS;
|
||||
private boolean injectionCachingEnabled = true;
|
||||
private boolean wrapperEnabled = true;
|
||||
private boolean eventConversionEnabled = true;
|
||||
private boolean dependencyTrackingEnabled = true;
|
||||
|
||||
/**
|
||||
@@ -57,11 +59,18 @@ public class GraalJSScriptEngineConfiguration {
|
||||
* @param config configuration parameters to apply to JavaScript
|
||||
*/
|
||||
void modified(Map<String, ?> config) {
|
||||
boolean oldInjectionEnabledForUiBasedScript = isInjectionEnabledForUiBasedScript();
|
||||
boolean oldDependencyTrackingEnabled = dependencyTrackingEnabled;
|
||||
boolean oldWrapperEnabled = wrapperEnabled;
|
||||
boolean oldEventConversionEnabled = eventConversionEnabled;
|
||||
|
||||
this.update(config);
|
||||
|
||||
if (oldInjectionEnabledForUiBasedScript != isInjectionEnabledForUiBasedScript()
|
||||
&& !isInjectionEnabledForUiBasedScript() && isEventConversionEnabled()) {
|
||||
logger.warn(
|
||||
"Injection disabled for UI-based scripts, but event conversion is enabled. Event conversion will not work.");
|
||||
}
|
||||
if (oldDependencyTrackingEnabled != dependencyTrackingEnabled) {
|
||||
logger.info(
|
||||
"{} dependency tracking for JavaScript Scripting. Please resave your scripts to apply this change.",
|
||||
@@ -72,6 +81,16 @@ public class GraalJSScriptEngineConfiguration {
|
||||
"{} wrapper for JavaScript Scripting. Please resave your UI-based scripts to apply this change.",
|
||||
wrapperEnabled ? "Enabled" : "Disabled");
|
||||
}
|
||||
if (oldEventConversionEnabled != eventConversionEnabled) {
|
||||
if (eventConversionEnabled && (!isInjectionEnabledForUiBasedScript() || !wrapperEnabled)) {
|
||||
logger.warn(
|
||||
"Enabled event conversion for UI-based scripts, but auto-injection or wrapper is disabled. Event conversion will not work.");
|
||||
}
|
||||
if (!eventConversionEnabled) {
|
||||
logger.info(
|
||||
"Disabled event conversion for JavaScript Scripting. Please resave your scripts to apply this change.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +106,8 @@ public class GraalJSScriptEngineConfiguration {
|
||||
injectionCachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_CACHING_ENABLED), Boolean.class,
|
||||
true);
|
||||
wrapperEnabled = ConfigParser.valueAsOrElse(config.get(CFG_WRAPPER_ENABLED), Boolean.class, true);
|
||||
eventConversionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_EVENT_CONVERSION_ENABLED), Boolean.class,
|
||||
true);
|
||||
dependencyTrackingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_DEPENDENCY_TRACKING_ENABLED),
|
||||
Boolean.class, true);
|
||||
}
|
||||
@@ -111,6 +132,10 @@ public class GraalJSScriptEngineConfiguration {
|
||||
return wrapperEnabled;
|
||||
}
|
||||
|
||||
public boolean isEventConversionEnabled() {
|
||||
return eventConversionEnabled;
|
||||
}
|
||||
|
||||
public boolean isDependencyTrackingEnabled() {
|
||||
return dependencyTrackingEnabled;
|
||||
}
|
||||
|
||||
+9
-1
@@ -100,6 +100,7 @@ public class OpenhabGraalJSScriptEngine
|
||||
}
|
||||
}
|
||||
private static final String OPENHAB_JS_INJECTION_CODE = "Object.assign(this, require('openhab'));";
|
||||
private static final String EVENT_CONVERSION_CODE = "const event = (typeof this.rules?._getTriggeredData === 'function') ? rules._getTriggeredData(ctx, true) : this.event";
|
||||
|
||||
private static final String REQUIRE_WRAPPER_NAME = "__wraprequire__";
|
||||
/** Shared Polyglot {@link Engine} across all instances of {@link OpenhabGraalJSScriptEngine} */
|
||||
@@ -329,7 +330,14 @@ public class OpenhabGraalJSScriptEngine
|
||||
protected String onScript(String script) {
|
||||
if (isUiBasedScript() && configuration.isWrapperEnabled()) {
|
||||
logger.debug("Wrapping script for engine '{}' ...", engineIdentifier);
|
||||
return "(function() {" + System.lineSeparator() + script + System.lineSeparator() + "})()";
|
||||
|
||||
String eventConversionScript = "";
|
||||
if (configuration.isEventConversionEnabled()) {
|
||||
eventConversionScript = EVENT_CONVERSION_CODE + System.lineSeparator();
|
||||
}
|
||||
|
||||
return "(function() {" + System.lineSeparator() + eventConversionScript + script + System.lineSeparator()
|
||||
+ "})()";
|
||||
}
|
||||
return super.onScript(script);
|
||||
}
|
||||
|
||||
+10
@@ -39,6 +39,16 @@
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="eventConversionEnabled" type="boolean" required="true" groupName="environment">
|
||||
<label>Convert Event from Java to JavaScript type in UI-based scripts</label>
|
||||
<description><![CDATA[
|
||||
Converting the event data from Java to JavaScript types in UI-based scripts allows working with event data in a native JS way without special handling for Java types.<br>
|
||||
With this option enabled, the event data available in UI-based scripts is all JS types and the same as in file-based scripts.<br>
|
||||
Please note that this option <strong>requires both auto-injection & wrapper enabled</strong> and only applies to UI-based scripts and does not affect file-based scripts.
|
||||
]]></description>
|
||||
<default>true</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="injectionCachingEnabled" type="boolean" required="true" groupName="system">
|
||||
<label>Cache openHAB JavaScript Library Injection</label>
|
||||
|
||||
+2
@@ -7,6 +7,8 @@ addon.jsscripting.description = This adds a JS (ECMAScript-2024) script engine.
|
||||
|
||||
automation.config.jsscripting.dependencyTrackingEnabled.label = Enable Dependency Tracking
|
||||
automation.config.jsscripting.dependencyTrackingEnabled.description = Dependency tracking allows your scripts to automatically reload when one of its dependencies is updated. You may want to disable dependency tracking if you plan on editing or updating a shared library, but don't want all your scripts to reload until you can test it. Please note that changing this setting only applies to scripts loaded after the change.
|
||||
automation.config.jsscripting.eventConversionEnabled.label = Convert Event from Java to JavaScript type in UI-based scripts
|
||||
automation.config.jsscripting.eventConversionEnabled.description = Converting the event data from Java to JavaScript types in UI-based scripts allows working with event data in a native JS way without special handling for Java types.<br> With this option enabled, the event data available in UI-based scripts is all JS types and the same as in file-based scripts.<br> Please note that this option <strong>requires both auto-injection & wrapper enabled</strong> and only applies to UI-based scripts and does not affect file-based scripts.
|
||||
automation.config.jsscripting.group.environment.label = JavaScript Environment
|
||||
automation.config.jsscripting.group.environment.description = This group defines JavaScript's environment.
|
||||
automation.config.jsscripting.group.system.label = System Behaviour
|
||||
|
||||
Reference in New Issue
Block a user