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

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 !

I have found that if you have more than four arguments to a lambda it is a code smell. There is almost surely a better way to do what you want using fewer arguments or without using lambdas by taking advantage of Groups, storing state in Items, and or using other tricks to combine the rules that use the lambdas rather than having the rules call the same lambdas.

i eventually ended up writing the whole rule, then search and replace for other objects…

i wanted to calculate tank volume , percentage, etc using ultrasonic distance, dimensions of the tank (WxHxD) and tapping point of the pipe and tank type (in case if it was cylindric) . that is total, the 7th is the result (item.postUpdate)

i know i could made it smaller, i just wanted to made it all in one shot

Thanks for the info! Where can I read more about the new rules engine?

Probably on the ESH forum or github page. It is experimental so there really isn’t any docs yet.

Just found this!

If you REALLY want to pass more than 6 arguments, you can use a simple trick of passing a hashmap to the lambda function, where the hashmap contains all the parameters you want to pass to the lambda. This overcomes the number of arguments restriction.

Just wanted to add something: A Function that does not return anything is a Procedure. So instead of using a Function one can use a Procedure. This is done in exactly the same way (only it does not return anything and consequently does not force you to return anything). Example using a Procedure that takes two arguments:

import org.eclipse.xtext.xbase.lib.Procedures

val Procedures.Procedure2<Integer, String> myProcedureWithTwoArguments = [someIntArg1, someStringArg2 |
	// do something and return nothing