Experimental Next-Gen Rules Engine Documentation 4 of : Writing Scripts

jsr223
ngre
Tags: #<Tag:0x00007f51e4f34160> #<Tag:0x00007f51e4f35b28>

(Rich Koshak) #1

[Edit: Moved the Actions to their own

Writing Scripts

As you can see a lot can be accomplished in the NGRE without writing scripts at all. Much more than is possible with just Items. However at some point writing a script will be required. We know that the scripts entered into the Script box need to be JavaScript but how do we do all the stuff we are used to in Rules DSL like creating Timers, calling Actions, working with Groups and Items?

JSR223

Remember when I wrote that users of JSR223 Rules are already using the NGRE? This is important because that means:

  • the default environment is the same as the default JSR223 Rules environment as documented here with a few additions described below
  • any JSR223 JavaScript library can be imported and used in our PaperUI scripts
  • we don’t have to write Rules in PaperUI at all, and we can mix and match if we want to (writing JSR223 Rules on their own is beyond the scope of this document)
  • features like enabling and disabling Rules should also work for JSR223 written Rules, not just PaperUI created Rules // TODO test this is actually true

JavaScript Resources

As previously mentioned, the only supported language is JavaScript. There are many tutorials and documents available as a reference to users to learn JavaScript. A few resources I found useful include:

Tutorial Why
Java 8 Nashorn Tutorial How to work with Java Objects inside the JavaScript. Most of the openHAB Objects we will be working with are Java, not JavaScript.
Java 8 Stream API Group member’s operations
TODO Need more JavaScript resources.

openHAB JSR223 Libraries We Can Use

We can rely upon the existing documentation for JSR223 Rules, in particular JSR223 JavaScript Rules and we can learn from and use openHAB JSR223 JavaScript Libraries. The library linked to through the docs is @lewie’s library located here and it provides many conveniences for writing Rules in JavaScript, almost all of which we can use in PaperUI created JavaScript Rules.

This library makes the JavaScript we write a lot more convenient and uses similar names and conventions as is used in the Rules DSL which will be useful for those users looking to migrate from Rules DSL. Note that there will not be a requirement to migrate from Rules DSL. Eventually Rules DSL will be one of the supported languages to run on the NGRE.

However, although the documentation for JSR223 is informative, there are certain things we don’t have to deal with in PaperUI defined Rules. Anything that deals with creating Rules or Rule Triggers is already handled for us. We just need to provide the body of the Rule to execute.

Using Libraries

First create the $OPENHAB_CONF/automation/jsr223 folder if it hasn’t been created already. This is where the libraries you need to use will go. This is also where jsr223 Rules will go.

Next download lewie’s library from here and place the files in $OPENHAB_CONF/automation/jsr223. You may want to remove all the files not in the jslib folder. The former are tests and examples which will start running and likely start throwing errors because you do not have certain Items defined. You may also want to remove the jslib/triggersAndConditions.js file. This file conflicts with the rule started trigger with PaperUI created Rules.

If you are running a non-installed OH (e.g. Docker, manual install, QNAP, Synology. etc) you may need to edit the library with the full path to the library. Search through the files for “/etc/openhab2” and replace that with $OPENHAB_CONF where $OPENHAB_CONF is the full path to your OH conf folder.

Watch openhab.log and you should see it loading the scripts. If you copied over the test and example files you may see them start executing in the logs. Until the path gets corrected as described above you will see errors as these try to run.

As you write your Scripts, you will want to reference the examples in the library to learn how to accomplish what you want to do. You will also probably want to learn a bit of JavaScript to learn how to do language specific things like how to construct while loops, process Strings, etc. See the resources above.

Once the library is successfully loading you can import it into your Script using a single line. Then all of the convenience functions created to let us write code that is a bit easier and a bit more familiar.

Here is the library version of the Hello World! script above.

load(Java.type("java.lang.System").getenv("OPENHAB_CONF")+'/automation/jsr223/jslib/JSRule.js');
logInfo("Hello World!");

The above assumes that the environment variable $OPENHAB_CONF is set to the full path to your conf folder. For installed OH that environment variable will be set for you.

Implicit Variables

Unlike with Rules DSL, not as much is made available to you by default. For example, we need to import the logger and we have to pull Items out of the Item Registry by name instead of having a pre-created variable we can use to access the Items. This is why convenience libraries like the previously mentioned one are so important.

On-the-other-hand, in some ways there is a lot more that is made available to us for use even without libraries. For example, if we write a Script that assumes that the Rule was triggered by a Group changing, we can write a Condition that doesn’t even attempt to run the Rule unless it is triggered by a GroupItemStateChangedEvent.

For the remainder of this document, both a version with and without the previously mentioned library will be shown.

What is Available in the Script?

The default environment provided matches the same default environment provided in JSR223 JavaScript rules. This includes all of the variables listed here. Notice that most of these are the enum type Commands and States like ON, OFF, UP, DOWN, etc. But there are a few variables that give us access to core parts of openHAB that we need.You can identify those as they are all lowercase.

There are also a few variables not listed in that table that are available to us in PaperUI defined Scripts. Those are documented in the following table:

Object Purpose
event Describes the event that caused the Rule to trigger. This variable does not exist unless the Rule was triggered by an Item or Channel event.
command The command that caused the Rule to trigger. This is equivalent to receivedCommand in Rules DSL. This variable does not exist unless the Rule is triggered by a command.
oldState The previous state of the Item. If it is UNDEF or NULL the variable will be blank when logged. This is equivalent to previousState in Rules DSL. It does not exist unless the Rule was triggered by an item changed trigger.
newState The current state of the Item. If it is UNDEF or NULL the variable will be blank when logged. This is equivalent to triggeringItem.state in Rules DSL. Only exists when the Rule was triggered by an item changed trigger.
state The current state of the Item. If it is UNDEF or NULL the variable will be blank when logged. This is equivalent to triggeringItem.state in Rules DSL. Only exists when the Rule was triggered by an item updated trigger.

There are a few things to note.

  • Time triggers will not have any of these variables defined.
  • When creating a Rule that has Item event triggers that calls another Rule as it’s action, these variables will be populated in the called Rule. For example, if I have the Rule “Hello World” triggered by “Item Foo received command” and the Rule “Calls Hello World” triggered by “Item Bar received update” which calls “Hello World” as its Action then command will be undefined and event will be “Item ‘Bar’ received update ON” in the “Hello World” Rule when “Calls Hello World” is triggered.

event is going to be one of the more useful variables. The event, when logged takes the following forms:

Event Form when Logged
a trigger channel fires <channel ID>#<event> triggered <EVENT TYPE>, for example: astro:sun:local:set#event triggered START
an item changes state <Item> changed from <Previous State> to <Current State>, for example: Test changed from ON to OFF. NOTE, if the state is UNDEF the state will be blank. If the Item is a Group then the form will be <Group> changed from <Previous State> to <Current State> through <Item>, for example: TestGroup changed from ON to OFF through Test
an item received a command Item '<Item>' received command <Command>, for example: Item 'Test' received command ON
an item state is updated <Item> updated to <State>, for example: Test updated to ON
it is a fixed time of day event will be undefined

But how do you use event? The event Object has the following methods/data members.

Method/Data Member Value
event.toString Prints the text described in the table above
event.itemName The name of the Item that triggered the Rule. undefined when triggered by a channel event. In Rules DSL this is triggeringItem.name.
event.itemState The state of the Item that triggered the Rule. undefined when triggered by a channel event or received command events. In Rules DSL this is triggeringItem.state.
event.oldItemState The previous state of the Item that triggered the Rule. Only has a value for changed triggered Rules.
event.memberName The member of the Group that caused the change to the Group that triggered the Rule. Only has a value for item changed triggers that use a Group.
event.type The event type that triggered the Rule. Types include ChannelTriggeredEvent, ItemStateChangedEvent, ItemCommandEvent, ItemStateEvent, and GroupItemStateChangedEvent. Note event doesn’t exist for non-Item and non-Channel triggered Rules.
event.payload JSON formatted description of the event.
event.source Appears to be always null
event.topic The event bus topic where the event was published: smarthome/channels/<channelID>/triggered, smarthome/items/<Item>/command, smarthome/items/<Group>/<Item>/statechanged

You will notice that most of the state related properties have an equivalent environment variable defined.

Beyond the state values, to get the name of the Item that triggered the Rule look at event.itemName. To get the Item Object associated with the event (e.g. triggeringItem in Rules DSL) use

// No library
var triggeringItem = ir.getItem(event.itemName);

// Library version
var triggeringItem = getItem(event.itemName);

You will also notice there are a lot of variables that only exist when the Rule is triggered by certain types of triggers. You can test in a script to see if a variable exists using:

// Both with and without library
if(command == undefined) // we know the Rule wasn't triggered by a command if true

Comparisons to Rules DSL

For those used to the Rules DSL, here is a table mapping what we have available to the implicit variables available in Rules DSL Rules.

Rules DSL NGERE Native NGRE Library Notes
receivedCommand command, event.command command, event.command Only if the Rule was triggered by a command
previousState oldState, event.oldItemState oldState, event.oldItemState Only if the Rule was triggered by a changed
triggeringItem ir.getItem(event.itemName) getItem("event.itemName") Only if the Rule was triggered by an Item event, if the Rule was triggered by a Group then triggeringItem is the Group
triggeringItem for a Member of triggered Rule ir.getItem(event.type.startsWith("Group") ? event.memberName : event.itemName); getItem(event.type.startsWith("Group") ? event.memberName : event.itemName); Only if the Rule was triggered by an Item event, if the Rule was triggered by a Group then triggeringItem is the member of the Group. There is no true Member of trigger so this will only work with Group changed and updated Rule triggers.
NA state, newState, event.itemState state, newState, event.itemState state only if the Item is triggered by an update, newState only if the Item is triggered by a change.
now var DateTime = Java.type("org.joda.time.DateTime");var now = function() { return DateTime.now(); };, now(); if using the library See DateTimeType below

Items Operations

Accessing Item Objects and States

Unlike in the Rules DSL where all the Items are available by name, in the NGRE (without using a library) we must pull the Item Object from the Item Registry (ir variable). The following data are available on the Item Object pulled from the Item Registry.

// No library
var MyItem = ir.getItem("MyItem");

// Library
var MyItem = getItem("MyItem");

However, if all you need is the Item’s state, there is a Map of Item names and their corresponding States available in the items Object.

// Both with or without library
var MyItemState = items["MyItem"];

The Item Object has a number of data members and methods available. The following table lists them along with their purpose. They can all be accessed in the usual way: e.g. MyItem.groupNames.

Data/Method Purpose
category The name of the icon(s) used to represent the Item on sitemaps.
groupNames Array of all the Groups the Item is a member of.
label The label of the Item excluding the [%s] parts.
name The name of the Item.
state The current state of the Item.
tags The tags applied to the Item.
type The Item type (e.g. “Switch”).
addGroupName Adds this Item to the given Group.
addGroupNames Adds this Item to the given list of Groups. // not tested
addTag Adds the given tag to the Item.
addTags Adds the given list of tags to the Item. // not tested
getStateAs Returns the state of the given Item as the given type. You must pass the type you want the state as. For example, to get a Color Item as an OnOffType use MyColorItem.getStateAs(OnOffType.class).
removeAllTags Removes all tags from the Item
removeGroupName Removes the Item from the given Group
removeTag Removes the given tag from the Item
setCategory Sets the icon for the Item // not tested
setLabel Sets the label for the Item // not tested
toString Prints out all the important parameters of the Item as a String

Some of these will not be useful or functional (e.g. I don’t think it works to setLabel from Rules).

TODO: Someone who uses these needs to test. Please try the “not tested” and update the table accordingly.

Commanding and Updating Items

Notice there isn’t a sendCommand or postUpdate method on the Item Object. As of this writing there is no method on the Items to do this. So we need to do this through the events object.

// No Libary
var it = ir.getItem("MyItem");
events.sendCommand("MyItem", ON);
events.sendCommand(it, ON);
events.postUpdate("MyItem", ON);
events.postUpdate(it, ON);

// Library
var it = getItem("MyItem");
sendCommand("MyItem", ON);
sendCommand(it, ON);
postUpdate("MyItem", OFF);
postUpdate(it, OFF);

// TODO submit a PR to add a sendCommandIfDifferent function to the library

Working with Item States

One of the current weaknesses of the NGRE and one that is sure to cause frustration is that the states carried by Items are Java Objects and the values in Rules you will want to compare them against will be JavaScript Objects and primitives. Occasionally this may cause problems. Consider this when errors occur.

Accessing States and Comparisons

Type Native Example
StringType if(items["TimeOfDay"] == "DAY")
DecimalType if(items["Temperature"] > 65), val result = items["Temperature"] + 5. To convert to int, float, et. al. use the “value” methods, e.g. val stAsInt = items["Temperature"].intValue.
PercentType See DecimalType
HsbType items["MyColor"].hue, items["MyColor"].saturation, items["MyColor"].brightness, var asDimmer = ir.getItem("MyColor").getStateAs(DecimalType.class), see DecimalType above for what you can do with the numbers
All Enum Types (e.g. OnOffType) if(items["MySwitch"] == ON)

DateTimeType
//////////////////////////////////////////////////////////////////////////////// TODO how to convert a DateTimeType to OffsetDateTime
DateTimeType is a special case that needs further discussion. In the old Rules DSL, most date and time operations were done with the Joda DateTime library. For the NGRE Joda has been replaced by Java’s OffsetDateTime. You will notice that most if not all of the methods you are used to from Joda are present.

If not using a convenience library (see below) you should add the following two lines to the top of your script.

var OffsetDateTime = Java.type("java.time.OffsetDateTime");
var now = function(){ return OffsetDateTime.now(); };

In order to do operations with OffsetDateTime such as plusMinutes or isAfter you need to convert the DateTimeType to an OffsetDateTime.

var asODT = OffsetDateTime.parse(items["MyDateTime"].toString()); // Didn't work

TODO How to I pass the DateTimeType.toString() to the OffsetDateTime.parse

//////////////////////////////////////////////////////////////////////////////////////
DateTimeType is a special case that needs further discussion. In the old Rules DSL, most date and time operations were done with the Joda DateTime library. We can access the Joda DateTime from our NGRE scripts.

If not using a convenience library (see below) add the following lines to the top of your script.

// No library
var DateTime = Java.type("org.joda.time.DateTime");
var now = function() { return DateTime.now(); };

// Nothing to do with library

In order to do operations with now and date and time comparisons like plusMinutes and isBefore the DateTimeType needs to be converted to a Joda DateTime.

// With and without library
var jdt = new DateTime(items["MyDateTime"].toString());
if(jdt.isBefore(now().minusMinutes(10)) {
...

See the Joda DateTime JavaDoc for the full list of methods available.

There will be times where one needs to take a Joda DateTime back to a DateTimeType.

NOTE: This is going to change in the near future and Joda will no longer be required.

// With and without library
var dtt = new DateTimeType(now().toString());

Accessing Persisted Data

Like the sendCommand and postUpdate methods, there are no historic state methods on the Item Object. If not using a library, you need to import the PersistenceExtensions and make the calls to previousState, lastUpdate, etc. through that Object.

Add the following to the top of your script if not using the library.

var pe = Java.type("org.eclipse.smarthome.model.persistence.extensions.PersistenceExtensions");

All of the methods discussed in the Persistence documentation are available on PersistenceExtensions with the primary difference being one must pass the Item as the first argument to the method. For example:

// Without library
var prev = pe.previousState(ir.getItem("MyItem"), true, "influxdb");

// With library
var prev = previousState("MyIem", true, "influxdb");

Remember, many of these return a HistoricItem so to get the previous state, whether or not it is different from the current state of the Item from the default persistence:

// Without library
var prevState = pe.previousState(ir.getItem("MyItem")).state;
var prevTime = pe.previousState(ir.getItem("MyItem")).timestamp;

// With library
var prevState = previousState("MyItem").state;
var prevTime = previousState("MyItem").timestamp;

Functions

Functions is a unit of code that can be independently called. One can pass arguments to the unit. In Scripts functions server three purposes:

  • encapsulate a set of code that needs to be called more than once within a given Script
  • pass to another function (see Group Operations for examples)
  • creating a library that can be used used across multiple Rules (see above for how to include a library and Writing Libraries later in this tutorial for how to create a library)

Creating a function in JavaScript is through the keyword function.

var namedFunction = function() {
    // some JavaScript code
    return returnVal;
};

var functionWithArguments = function(arg1, arg2) {
    // some JavaScript code
    return returnVal;
};

Anonymous function, see Group Operations below

However, unless you put the functions into a library (see next sections) the function will only be available to the Action script where it is defined. To create reusable functions you must put them into a library and import them into your Action Script as illustrated above.

One thing to note is that in NGRE, functions are not the only way to encapsulate a set of code to be called and reused. Since Rules can trigger Rules, one can encapsulate the code in a Rule.

TODO: These are not working for me right now I may need an update?

Function Description
rules.runNow("<rule UID>"); Executes the Rule with the given UID immediately. The conditions of the Rule will be honored and there will be no variables in the cxt.
rules.runNow("<rule UID>", <true/false>, Map<String, Object>) Executes the Rule with the given UID immediately. The conditions of the Rule will be honored if true is passed, ignored if false is passed. The Map contains key/value pairs that will be available in the called Rule in the ctx variable.

Group Operations

One of the more powerful tools for the writing of generic reusable Rules is through the use of Groups. By triggering the Rule based on changes to a Group and looking at the topic as described above, one can determine what Item triggered the Rule and write one Rule to work for many Items.

The NGRE also has an alternative way to write generic Rules. Once can write one Rule and then call that one Rule from other Rules using different triggers.

The first thing to realize is that a Group Item is still an Item. So everything in the Items Operations section applies to Groups.

Once in a Rule, there are a number of operations one may want to do on a Group’s members. One important thing to note is that members and allMembers is a java.util.List, not a JavaScript array. So standard JavaScript array operations are not available.

Nashorn JavaScript provides access to the stream() method on collections and JavaScript lets us create anonymous functions inline which lets us do the same sorts of operations with Groups that we are used to. There are more operations than those listed in this table. Search for more examples and tutorials on Java’s Stream API for complete documentation.

Operation What it does
filter var filtered = group.members.stream().filter(function(i){ return <boolean expression>; }).toArray(); where <boolean expression> is the filtering conditional.
findFirst var first = group.members.stream().filter(function(i) { <boolean expression>; } ).findFirst().get();, you can test whether or not the filter has any elements using isPresent() on the result of findFirst().
forEach for each (var i in group.members) { <do stuff>; } alternative: group.members.stream().forEach(function(i) { <do stuff>; } );
map var names = gr.members.stream().map(function(i){ return i.name; }).toArray(); get a List of all the names of Items in members. If you will be doing a map/reduce you don’t need the toArray the result of the map
reduce var names = gr.members.stream().map(function(i){ return i.name; }).reduce(function(acc, next) { return acc + " " + next; }).get(); As with the findFirst, the reduce returns an Optional object and we can test if there was a result using isPresent().

There may be times when you want to deal with JavaScript arrays instead of Java Collections.

// With and without library
var jsarray = Java.from(group.members); // converts the Java List<Item> to an Item[ ] JavaScript array

Previous step: Experimental Next-Gen Rules Engine Documentation 3 of : Your First Rule
Next step: Experimental Next-Gen Rules Engine Documentation 5 of : Actions


Experimental Next-Gen Rules Engine Documentation 5 of : Actions
Understanding / Avoiding ERE (Experimental Rule Engine)
Javascript JSR223 with Next-Gen Rules Add-on not working
Javascript JSR223 with Next-Gen Rules Add-on not working
[SOLVED] Select data type in New Rule Engine
Lambda calling other lambda? (JSR223/JYTHON)
(Rich Koshak) #2

Edit: I figured it out. I need to use .class as in OnOffType.class and it works like a charm.

Pretty soon I’ll be able to start migrating my Rules and the DPs over to the NGRE, at least those that don’t require cron polling, Member of MyGroup received command or global variables, though I think I can use Item metadata for some of that, just not for things like Timers.

@5iver, @lewie, have either of you tried to use getStateAs? This is particularly useful for people with Color Items or Dimmer Items that they want to test whether it is ON or not.

Everything I’ve tried to does just doesn’t work, generating an error.

Also, do you know if setLabel and setCategory work or not in JSR223. I’m 90% positive they do not work in Rules DSL.

Thanks.


(Helmut Lehmeyer) #3

Added some “Call an Action” content. What do you mean with: “third party installed Actions”. Self installed or self written Actions?

To get an parse Members, I do:

		var members = getItem("g_GROUPNAME").getAllMembers();
		logInfo( members.class ); 
		members.forEach(function(key) {
			logInfo( "Prints a toString output: "+ key );
			logInfo( "As everything in JS: "+ typeof key );
			logInfo( "OpenHAB Item class: "+ key.class );
			logInfo( "OpenHAB Item Type: "+ key.getType() );
			logInfo( "Item Name: "+ key.getName() );
			logInfo( "Item State: "+ key.getState() ); 
		});

getStateAs should work, like all other public Functions of org.eclipse.smarthome.core.items.GenericItem.

org.eclipse.smarthome.core.items.GenericItem.GenericItem(String, String)
getState()
getStateAs(Class<T>)
getUID()
getName()
getType()
getGroupNames()
addGroupName(String)
addGroupNames(String...)
addGroupNames(List<String>)
removeGroupName(String)
dispose()
setEventPublisher(EventPublisher)
setStateDescriptionService(StateDescriptionService)
setUnitProvider(UnitProvider)
setItemStateConverter(ItemStateConverter)
setState(State)
send(RefreshType)
toString()
addStateChangeListener(StateChangeListener)
removeStateChangeListener(StateChangeListener)
hashCode()
equals(Object)
getTags()
hasTag(String)
addTag(String)
addTags(Collection<String>)
addTags(String...)
removeTag(String)
removeAllTags()
getLabel()
setLabel(String)
getCategory()
setCategory(String)
getStateDescription()
getStateDescription(Locale)
isAcceptedState(List<Class<? extends State>>, State)

What I could not find out: Why execute function in SimpleRule, does not give back a usable trigger event Object like: event.command, event.oldState, event.newState, event.state

In old OH1 JSR232 I could get exactly this event object. But in openHAB2 I do not find the reason for the sheer number of abstract classes and interfaces


(Rich Koshak) #4

I mean calling Actions that need to be installed and not the ones that come with OH, for example, publish from the MQTT Action.

There may not be any difference but I had the comment there to remind myself to test more than just the built in Actions.

The problem I had was trying to figure out how to pass the Class to the method. In Rules DSL I just need to pass OnOffType, for example. I was apparently having a slow brain day because it took me forever to remember there should be a .class on OnOffType.

I can already see that dealing with the differences between Java Types and JavaScript Types is going to be a problem, particularly for non-technical users.

Thanks for the edit and the info!


(Rich Koshak) #5

I just downloaded the latest version of your library (createTimer didn’t work on the version I was working with) and I can’t get the createTimer function to work. It isn’t throwing errors any more but runme never runs. Is there a bug or anything I might be doing wrong?

Thanks!


(Yannick Schaus) #6

Some additional insight on “What is Available in the Script” that I gathered from reading through a lot of the code in org.eclipse.smarthome.automation a year ago :slight_smile: I would edit the wiki but I’m not sure how, it’s a little low level… but here it is anyways, it might help understand better how it all works.

Here you’ll see 2 more variables added to the execution context:

  • ruleUID which is the UID of the current rule
  • ctx which is an object containing the current “context”

You will find an example of using the “ctx” variable here: https://github.com/eclipse/smarthome/blob/master/bundles/automation/org.eclipse.smarthome.automation.module.script.test/ESH-INF/automation/rules/DemoScriptRule.json

As far as I understood, outputs of each trigger and action modules (which are different for each module type, you can have a look at http://openhab:8080/rest/module-types to determine the ouputs defined for a given module type) are added to the context with the ID of the module as a prefix in the format "<moduleID>.<variable>"

Therefore, if you have a module of type core.ItemStateChangeTrigger with an ID of trigger1, since that module type has 3 outputs, newState, oldState and event, you can access those outputs in the scripts with ctx['trigger1.newState'] (attention, this is different than ctx.trigger1.newState which won’t work!), ctx['trigger1.oldState'], ctx['trigger1.event'].

Now later in the setExecutionContext method linked above, you have this code:


That’s basically what gives you the global variables like newState, oldState, event which differ depending on type of the trigger. If there are collisions (multiple outputs with the same name for different modules) then which one ends up as a global variable is unclear.

Two things I found interesting but haven’t really explored:

  1. script actions has a “result” output too, which is the result of the script, so if you have multiple script actions in a sequence, their IDs being let’s say script1 and later script2, you can potentially access the result of the first script in the second script with ctx['script1.result'] - this could be an simple type, or an array, or an object to reuse in subsequent scripts;
  2. The core.RunRuleAction (“run rules”) action forwards the context of the current rule to the rules it runs! This means, if all modules have unique IDs, you may be able to chain rules and still access outputs of modules of the calling rules (for instance, determine what was the oldState in the trigger of the original rule in a rule that was run from it). This was very interesting for my “flows builder” because it relies heavily on the “run rules” action to build flows into rules chained together.

Hope this helps!


(Helmut Lehmeyer) #7

Do you have a example, where you need to use OnOffType.class?
You can use short ON and OFF like in Rules DSL.
If you use load(Java.type("java.lang.System").getenv("OPENHAB_CONF")+'/automation/jsr223/jslib/JSRule.js'); in helper.js I have added other types as shortcut:
context.UnDefType = UnDefType;
context.OPEN = OpenClosedType.OPEN;
context.CLOSED = OpenClosedType.CLOSED;
context.REWIND = RewindFastforwardType.REWIND;
context.FASTFORWARD = RewindFastforwardType.FASTFORWARD;
context.PLAY = PlayPauseType.PLAY;
context.PAUSE = PlayPauseType.PAUSE;
context.NEXT = NextPreviousType.NEXT;
context.PREVIOUS = NextPreviousType.PREVIOUS;

Exactly therefore these users can use simplified JSRule.js. This works in many parts similar to Rules DSL, isn’t it?

JSRule({
	name: "filename L"+__LINE__,
	description: "Simple JSRule L:"+__LINE__,
	triggers: [ 
		TimerTrigger("0/15 * * * * ?")
	],
	execute: function( module, input){
		logInfo("################ "+me+" Line: "+__LINE__+"  #################");
		sendCommand("testItemSwitch", OFF);		
	}
});

(Helmut Lehmeyer) #8

Added https://github.com/lewie/openhab2-javascript/blob/master/TimerExample.js.
Please show me your not working timer example.

And yes, I could find trigger-event now… had forgotten: :wink:
helper.js: getTriggeredData(input) parses input for better use in rules.

var myRule = JSRule({
	name: me+" Trigger events",
	description: "Trigger events  L:"+__LINE__,
	triggers: [ 
		//TimerTrigger("0/15 * * * * ?"),
		ItemStateChangeTrigger("testItemSwitch")
	],
    execute: function( module, input){
		logInfo("Trigger events "+__LINE__+" - "+ module);

		logInfo(" -- input "+ input);
		logInfo(" -- oldState "+ input.oldState);
		logInfo(" -- newState "+ input.newState);
		logInfo(" -- event "+ input.event); //event=testItemSwitch changed from OFF to ON
		logInfo(" -- event "+ getTriggeredData(input).triggerType); //See: helper.js line 270

		logInfo(" -- getTriggers ", myRule.getTriggers());
		logInfo(" -- getConditions ", myRule.getConditions());
		logInfo(" -- getActions ", myRule.getActions());
		logInfo(" -- getConfigurationDescriptions ", myRule.getConfigurationDescriptions());
		logInfo(" -- getConfiguration ", myRule.getConfiguration());
		logInfo(" -- getTemplateUID ", myRule.getTemplateUID());
		logInfo(" -- getVisibility ", myRule.getVisibility());
    }
});

(Helmut Lehmeyer) #9

Thank You for this info!
In SimpleRule, which is use for JSRule https://github.com/lewie/openhab2-javascript/blob/master/jslib/JSRule.js#L88, does not provide context object yet.


(Rich Koshak) #10

I’ve seen that but I couldn’t think of a good use for it yet so I’ve been ignoring it. The rest of your post points to some powerful uses. I’ll have to come back to this for sure.

I’ve seen context but since it appeared to have the same information as event and event had more information so I’ve been focusing on event instead.

It might make more sense to use ctx though.

Ctx coupled with the rule chaining may be an approach to getting global vars like in Rules DSL back. I’ll need to explore.

Tremendously. Thanks!

if(ir.getItem(“MyColorItem”).getStateAs(OnOffType.class) == ON)

I noticed that. But in the above case I really do b need the class so I can, for example, check if a Color Item is ON without needing to mess with getting the brightness value from the HDBType. It’s particularly useful when you have a Group of different types of lights and want to check them all as if they’re were just Switches.

So far mostly but there are some oddities where understanding that you are working with Java becomes important. For example, see the whole Group Operations section. Understanding that members is a Java Collection and not a JS array is vital to understand how to work with them with the stream methods we are used to in Rules DSL. I can’t think of any way that this can be abstracted through a library. Maybe by creating some more functions in the context that we pass the collection and function, though that would bear the stream.

The example above in the OP is copy and paste from my rule. There are no errors but runme never runs.

One thing Scott brought up is that Rule templates have just had a bug fix. I’m hoping that we can figure it a way to convert the library to a template that perhaps even gets distributed as part of OH. Then the user doesn’t need to do anything at all to install the library. They just need to create a rule with the library template and all the convenience methods will be there.

Also, there are some conflicts in the triggers library with the triggers for the JSON rules that need to be sorted out. No parts of the library that deal with triggers or rules will ever be used in JSON rules because that is handled differently.

Thanks for all the info. It’s slow going but I’m starting to get enough reason material to build some comprehensive docs so some of the more adventurous users can start to experiment…


(Scott Rushworth) #11

Here’s one (enabling/disabling rules)…


(Yannick Schaus) #12

No worries, my intent was only to explain why you have an event available in the script scope in the first place - it is actually one of the outputs of the trigger that was actually executed, which is added to the context as ctx['triggerID.event'] (most triggers are based on the event bus so they “expose” an event “variable” as an output) and then flattened- their prefix with the module ID is removed and they’re added to the scripts’ scopes as globals.


(Helmut Lehmeyer) #13

Ah, now I understand. Yes, for getStateAs OnOffType.class directly is needed!

Yes, I think too, the Library should help Beginners and should simplify daily scripting for all. We should always be guided by the simplified working methods of Rule DSL. But everything we cannot catch.

By the way Library:

corrected createTimer, added setTimeout
and Improved createTimer section.

added HTTPRequestExamples
and Improved HTTP Requests section.

Regards


(Rich Koshak) #14

@5iver, do you know anything about rules.runNow? It is documented in the ESH JavaDoc but it doesn’t appear in the head source code baseline of ESH on https://github.com/eclipse/smarthome/blob/7206768ec41739ebf6512509260bf0ee29ccf22e/bundles/automation/org.eclipse.smarthome.automation.api/src/main/java/org/eclipse/smarthome/automation/RuleRegistry.java.

Is this something coming down the pike or something that has been removed or something you don’t know anything about.

On a related note, how does one add a tag to a Rule? There isn’t anything in PaperUII don’t see anything in the REST API. I see where I can add tags in the JSONDB files and I can add them there by hand. Since the Rule UIDs don’t exactly roll off the tongue I am assuming the use case for a library of Rules would be something like:

  • pull the collection of Rules with a given tag
  • filter/find the Rule by name
  • get the UID of the Rule
  • call rules.runNow(ruleUid)

It’s not super clean and I’d like to see something a little simpler but I can make it work like this.


(Scott Rushworth) #15

This is what Paper UI calls to run a rule, or it can be used to run a rule from a script.


quote=“rlkoshak, post:14, topic:55963”]
On a related note, how does one add a tag to a Rule?
[/quote]

Here is an example with Jython. I recently added tags to the rule decorator.


(Rich Koshak) #16

But that doesn’t explain why it only appears in the javadocs and doesn’t appear in the master of ESH’s source code or at least it didn’t when I looked at it last night. When I try to call it I get a no such function error. It’s clearly there in the link you provided though. Weird.

I’m running about a week old snapshot. I’ll upgrade and try again.

Oh, wait, it’s protected. Maybe that is why I can’t call it?

Unfortunately that doesn’t help me much in the PaperUI Rules. I’ll have to submit a PR to add this to the REST API and the UI.

Thanks!


(Scott Rushworth) #17

(Rich Koshak) #18

Just updated to the latest snapshot. I still get

2018-11-23 09:32:02.666 [ERROR] [internal.handler.ScriptActionHandler] - Script execution failed: TypeError: rules.runNow is not a function in <eval> at line number 9

for the line

rules.runNow("2dcda663-9e37-4519-b5bc-3abe6911c55f");

Where the String passed is the UID of a Rule that exists in my setup.


(Scott Rushworth) #19

I haven’t used runNow, as I can just call a rules action by calling the function in JSR223-Jython, but I did a quick test and this is working. However, I didn’t see anything similar for OSGI services in the JS libraries.

from openhab import osgi
ruleEngine = osgi.get_service("org.eclipse.smarthome.automation.RuleManager")
ruleEngine.runNow("9ede01b8-bbb7-43a1-9679-1fad663b7090")

(Rich Koshak) #20

Hmmmm. It does seem that RuleManager is more appropriate for access to Rules in Rules than RuleRegistry. But I too cannot find any way to access the osgi services.

But if that is the Interface that I should be using, why isn’t rules a RuleManager instead of a RuleRegistry?

I opened a few Issues, one of which covers this problem. We’ll see what the devs have to say.

Thanks!