Is there a way to create functions/routines in openhab?

I have a set of commands I’d like to run. They exist as lines in my .rules file. I keep calling it often in my rules. Can I condense these into a routine or a function? So I can call it with just one line?


See the wiki example.

You have already been using lambdas. The body of create timer is a lambda.

There are some limitations to be aware of.

Global lambda have no access to variables or items so j you have to pass everything you want to reference to it (unlike timer lambda which get a copy of everything in the current context). Secondly you can only pass up to six arguments. Thirdly, errors are difficult to debug so use lots of logging.

1 Like

On the point of rules and reusable functions and the issues mentioned above - is there any work planned/being done in this area? Or some kind of long term strategic roadmap/vision? Maybe @Kai can answer?

The reason I ask is I’m working on creating rules and functions that integrate with my speech processing setup. Depending on the “intent” returned by the speech processing I plan to call a different global lambda function which send commands to abstract (unbound) items that themselves have rules that implement the real command on the real item.

Should I be looking at this a different way? For example is this something the concept of things and channels could simplify?

Lambdas and how they are implemented in the rules engine come straight from the base language, Xbase. I know a rewrite of the rules engine is in the works for OH2 but I doubt that effort will go so far as adding features to this third party developed language.

From what you described, without more detail, I didn’t see anything that would prevent you from using lambdas to implement. I’ve found in the few cases where I’ve needed more than six arguments to pass to a lambda that using a hasmap to store them and passing that worked. Even better is when I put my items in groups and foreach or filter on the group instead of passing a bunch of items.

Look at the VoiceCommand binding and search these forums as well to see how others are solving this same problem.

Finally, a new binding may be more appropriate than implementing everything in rules.

If you do not need to pass any parameters (i.e. it is really just a routine and not a function), you can create a *.script file, which you can then execute from any rule through callScript().

I definitely will not try to introduce new features in Xbase.
The approach of the new rule engine (which I am currently heavily working on) is rather to allow the definition of “logic modules”, which can then be shared as a template or reused within different rules. I am not sure though, if this meets your requirements. Another focus is though, to support different scripting languages besides Xbase, i.e. making the openHAB 1 Jsr223 support a core part of the concept.
Btw, did you check if the Jsr223 rule engine might be a better match for your requirements? It allows writing Javascript or Python and it should be possible to define sub-functions there.

1 Like

Thanks Kai.

If you have a array list declared in your rules file

val List<Integer> Score

How do you access the .get() function inside the script file? I get this error in designer.

- Couldn't resolve reference to JvmIdentifiableElement 'Score'.
- Couldn't resolve reference to JvmIdentifiableElement 'get'.

Are script files only capable of accessing things defined in .item file?

Eric, this works for me:

	val score = newArrayList(1, 2, 3)
1 Like

Are there any more information to be found regarding the new rule engine? Perhaps a Github issue that one could track?

Yes, here:

i try to use lambda functions for my heating scheduling plan. For debugging purposes i want to use logging, but i have no idea how to use. Here is an short exerpt of my code:

val org.eclipse.xtext.xbase.lib.Functions$Function2 heatingScheduler = [LinkedHashMap<ArrayList<ArrayList<NumberItem>,String,String,Number,Boolean>> timingMap,
SwitchItem globalholiday| //Do Something]

The logging commands are

logDebug(<name>, <message>)
logInfo(<name>, <message>)
logWarn(<name>, <message>)
logError(<name>, <message>)

Where <name> is a name you come up with to help organize your log statements (I usually use the name of the rules file the statement is in) and <message> is the logging statement. You can use these in your lambda or in our rules.

thanks for your support. I know the logging commands but if i use them in an lambda function i get the following error in designer:

Incompatible implicit return type. Expected java.lang.Object but was void

That is a tricky gotcha with lambdas. The result of the last statement of the lambda is treated as a return value but if your last statement doesn’t return anything (i.e. void) you will get this error. You can fix this by just adding “true” as the last line of your lambda, after your logging statement.

1 Like

Great, this helps me to handle some more lines. Now i get crazy with an embedded foreach statement:

val org.eclipse.xtext.xbase.lib.Functions$Function2 heatingScheduler = [LinkedHashMap<ArrayList<ArrayList<NumberItem>,String,String,Number,Boolean>> timingMap,
	SwitchItem globalheatingperiod|
    if (globalheatingperiod.state == ON) {
		logInfo("HomeBox.HeatingScheduler","Heating period: " + GlobalHeatingPeriod.state)
		logDebug("HomeBox.HeatingScheduler","HashMap defined: " + timingMap.toString)
		timingMap.forEach[k,v |
			logInfo("HomeBox.HeatingScheduler","Starting loop for definition: \"" + k.toString + "\"")
			var String p2 = v.get(1) as String
		logInfo("HomeBox.HeatingScheduler","Stopped loop ")

Nothing inside the foreach loop is executed … grml any idea?

I didn’t know you can do a foreach on a hashMap like that. I’ve never seen it and can’t find in the documents whether it is valid or not.

In case it isn’t, try:

timingMap.keySet.forEach[k |
    logInfo("HomeBox.HeatingScheduler", "Starting loop for definition: " k.toString + "\"")
    var String p2 = timingMap.get(k).get(1) as String

Also, make sure that timingMap actually has some entries by logging timingMap.size or something like that.

NOTE: I may have the call to keySet and size not quite names right. I don’t have designer with me right now.

Is there not a way to make variables globally available to functions?

In my case, I’m still having to duplicate a decent amount of code in the rules in order to define the variables for the function.

By the way, is there a way for a single rule that applies to multiple items (either a group or items separated by OR statements) to identify which particular item triggered it? I know the point of functions is to address this, but being able to identify the item would negate my need for a function.

Two ways. Make the variable be an Item or put all the rules that depend on the global variable in the same file. That’s it.

I highly recommend the Item approach.

You can do it indirectly but only for Items in a Group and only if you have persistence configured to save at least every change to the Items in question.

Thread::sleep(250) // experiment to see how long your Persistence needs to save the change
val triggeringItem = gMyGroup.members.sortBy[lastUpdate].last

There are some limitations. If Items in the group can trigger really close together (i.e. faster than the sleep) the wrong item will be identified. You may need to cast the result from last to the right Item type (e.g. .last as SwitchItem).

Is it possible to make reusable “programs”/automation, that is, not only functions, but logic that may be reused in several instances? Say e.g. a thermostat which can be configured with some items (relays and sensors) and settings (setpoint and mode), and be instantiated for several rooms?

Not like you want, yet. This is a key feature of the new Rules engine that is currently experimental in OH 2.

For the default Rules DSL your only options are:

  • scripts, though you can’t pass anything out get a return value (not positive about that jar part) to them so they are of limited use

  • lambdas, though they are limited to being called by rules in the same file.

  • separation of behaviors design pattern
    Design Pattern: Separation of Behaviors

So i can’t create a function with more 6 arguments ? Argh !