Lambda, reusable rules and combining different items ... in one is it possible

OpenHAB2


After reading about ‘reusable rules via functions’ and ‘Combining different items’ (more info https://github.com/openhab/openhab/wiki/Rule-example%3A-Combining-different-items)

I have some questions

A. in the function tempLogic, what is this code exactly doing?

var Map<String, Map<String, Object>> roomObj = fixtures.get(room)
		if (roomObj != null) {
			var Map<String, Object> valueObj = roomObj.get(value)
			if (valueObj != null) {
				var org.openhab.core.items.GenericItem inItem = valueObj.get("Item")
				if (inItem != null) {
					var doUpdate = false
					doUpdate = doUpdate || value == "Actual" && inItem.state > 0.0
					doUpdate = doUpdate || value == "Plan" && inItem.state > 0.0
					doUpdate = doUpdate || value == "Valve" && inItem.state > 0
					doUpdate = doUpdate || value == "Mode"
					doUpdate = doUpdate || value == "Battery"
					doUpdate = doUpdate && inItem.state != Uninitialized

					if (doUpdate) {
						fixtures.get(room).get(value).put("LastValue", inItem.state)
						fixtures.get(room).get(value).put("LastUpdate",
							DateTimeUtils::currentTimeMillis())
						logDebug("Heating", String::format("Value updated! %s --> %s --> %s um %s", 
							room, value, valueObj.get("LastValue"), valueObj.get("LastUpdate"))
					}

especially the part of the update

doUpdate = doUpdate   ||   value == "Actual" && inItem.state > 0.0

define doUpdate = doUpdate
B. reusable functions

// lambda expression that can be used as a function (here: with 3 parameters)
//   … used in update and createRoom

		for (value : fixtures.get(room).keySet()) {
			tempLogic.apply(room, value, fixtures)
		}
		tempOutput.apply(room, fixtures, outputItems)


is it possible to put in a separate function?

C. Can this be solved by lambda expression?

rule sysinit
when
	System started
then

	createRoom.apply("GF_WZ_WT", newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery,
		GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)
	createRoom.apply("GF_WZ_HT_Fenster", newArrayList(GF_WZ_HT_Fenster_default,
		GF_WZ_HT_Fenster_valve, GF_WZ_HT_Fenster_battery, GF_WZ_HT_Fenster_mode,
		GF_WZ_HT_Fenster_actual, GF_WZ_HT_Fenster_komplett), fixtures, outputItems)

...


		for (value : fixtures.get(room).keySet()) {
			tempLogic.apply(room, value, fixtures)
		}
		tempOutput.apply(room, fixtures, outputItems)
	}
end	


rule GF_WZ_WT_updated
when
	Item GF_WZ_WT_default received update	or
	Item GF_WZ_WT_battery received update	or
	Item GF_WZ_WT_mode received update		or
	Item GF_WZ_WT_actual received update
then
	var room = "GF_WZ_WT"
	
// same as  in : rule sysinit 
// createRoom.apply("GF_WZ_WT", newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery,
//		GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)
	createRoom.apply(room, newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery, 
		GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)

		for (value : fixtures.get(room).keySet()) {
			tempLogic.apply(room, value, fixtures)
		}
		tempOutput.apply(room, fixtures, outputItems)
end




doUpdate is a boolean meaning it can be true or false. When you make a comparison (e.g >, <, ==) the result is a boolean.

So the author, rather than create a really big and complicated collection of if else statement they are doing the comparisons and assigning them to doUpdate.

That particular line could be rewritten as:

if( doUpdate || value == "Actual" && inItem.state > 0.0){
    doUpdate = true
}
else {
    doUpdate = false
}

I don’t understand the question. That code is already calling two other reusable functions. You want to create yet another lambda to call those other ones?

You can do it but you have to pass everything that a lambda needs as an argument, this includes other lambdas that need to be called. There is a limit of six arguments to a lambda so that can become a problem. In his case it is almost problem because you have seven arguments you would have to pass: tempLogic, room, value, fixtures, tempOutput, outputItem.

Generally, when one has lambdas that call lambdas I personally treat it as a code smell. There is almost always a better way to code it.

I don’t know what you are trying to “solve” here. You are already using some lambdas here.

Typically a lambda is a good choice when you have lots of separate rules which all do the same thing. In that case you use the lambda so you don’t have to reproduce the code.

Perhaps if you explain a bit more about what exactly you hope to accomplish with lambdas.

@rlkoshak
Rich, first of all thanks for your quick response.
(and for your support … I see frequently answers coming from you on this forum)

Now I understand about the code : doUpdate = doUpdate || value == “Actual” && inItem.state > 0.0
This short way of coding, has it a name? (Are there more such things? A link to a website to avoid more questions like this one)

I would definitely not say that the code is bad.
I have a lot of respect for the code and just wants to understand and like to know why it is written this way.
Just try to analyze and understand it.

My applogize for my unclear questions.

A) reusable function
In every rule of “… update” and in de “sysinit” this part of code is re-used;

//(used in rule sysinit, rule GF_WZ_WT_updated, rule GF_WZ_HT_Fenster_updated, rule GF_WZ_HT_Wand_updated, …)

            for (value : fixtures.get(room).keySet()) {
                tempLogic.apply(room, value, fixtures)
            }
            tempOutput.apply(room, fixtures, outputItems)

B) lambda
Concerning lambda … thanks for the information

(used in rule sysinit, rule GF_WZ_WT_updated, rule GF_WZ_HT_Fenster_updated, rule GF_WZ_HT_Wand_updated, …)

// in : rule sysinit
createRoom.apply("GF_WZ_WT", newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery, // GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems) 

// in ….update
var room = "GF_WZ_WT";
boolean valve=false;
createRoom.apply(room, newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery, GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)

It is just an idea … but
the “fixtures” and “outputItems” are always the same.
The elements are

for example :

var room = "GF_WZ_WT";
boolean valve=false;
createRoom.apply(room, newArrayList(GF_WZ_WT_default, null, GF_WZ_WT_battery, GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)

var room = "GF_WZ_WT";
boolean valve=true;
createRoom.apply(room, newArrayList(GF_WZ_WT_default, GF_WZ_WT_valve, GF_WZ_WT_battery, GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)

My question is it possible to create a function via an other function.
In the past I had made a script with variables which create an excutable, which activate it with the correct arguments.

virtualCreateRoomFunction(“GF_WZ_WT”, bValve, fixtures, outputItems)

or

virtualCreateRoomFunction(“GF_WZ_WT”, bValve)

createRoom.apply($room, newArrayList($room”_default”, $room”_valve”, $room”_battery',  $room”_mode”, G $room”_actual”,  $room”_komplett), fixtures, outputItems)

and as result :

createRoom.apply(GF_WZ_WT, newArrayList(GF_WZ_WT_default, GF_WZ_WT_valve, GF_WZ_WT_battery, GF_WZ_WT_mode, GF_WZ_WT_actual, GF_WZ_WT_komplett), fixtures, outputItems)

And finally all rooms in a hashmap; with 2 or 4 arguments, so that every room can be created …

There is nothing special about this line of code to need a name. You are just performing a calculation and storing the result in a variable. The only uncommon part is the calculation is doing boolean math instead of arithmetic. It is fundamental programming.

You could make this a lambda but frankly I wouldn’t. It’s a simple for loop that is already calling two other lambdas. In this new lambda you would have to pass room, fuxtures, value, outputItems, AND, tempLogic and tempOutput. When all is said and done your aren’t saving much by turning it into a lambda.

Thank you very much.