File-level val/var declarations behave unexpectedly

The core problem here is strangeness with val and var and not being able to get two file-level identifiers refer to the same object instance.

The difficulties here have arisen because, at least as far as I have seen, and no matter the choices of val vs. var here

val Number x = 1
val Number y = x

results in a null value for y

This is certainly unexpected, especially as val is supposed to be immutable. Even if there was a trigger that could be used when the Rules were loaded from the file, you wouldn’t be able to change the value of y

The problem encountered was not that the second parameter was failing to be passed properly, but that it was null from the above.




Was:

Maybe I’m missing something, but trying to get to even modest code reuse in Rules is driving me nuts.

TL;DR

Designer indicates that Procedure2.apply() is the proper way to “call” the procedure/function referenced by a variable, in my case

void Procedure2.apply(GroupItem arg0, LinkedHashMap<String, Number> arg1)

This appears to fail at runtime with the second argument seen by the Procedure2 being null

Note that calling a one-argument Procedure1 in this way works as expected.


First off, why

  • 20 dimmers
  • 10 remotes
    • Each with six buttons
  • No desire to replicate code 10, 20, 60, or more times
  • Keep configuration readable, diff-able,

Please don’t give me the whole “You have to think differently” line. If you’ve got a system that’s based on the latest and greatest shiny objects of OSGi, Karaf, and a leading vendors IDE and proprietary languages, then it should give you basic Programming 101 concepts of modularity and reuse, even if you can’t define your own first-class objects.

Overview of Problem

So far, I have two tasks that are common over every remote:

  • Send a STOP command to all in a group of dimmers (which is blocked by the group)
  • For each dimmer in a group, look up the preset value in a map and, if not null, set the dimmer level to that value

Let’s look at these one at a time, to see what works, and what doesn’t.

First off, trusting http://docs.openhab.org/configuration/rules-dsl.html that the Xtend documentation somehow describes the acceptable rules syntax is a little optimistic. There is quite a bit that Xtend can do, that you can’t do in rules, such as defining your own classes and methods.

From what I can gather, somename.rules starts generating a class SomenameRules. The var and val statements become fields of the class, and the rule declarations become methods, names prefaced with an underscore.

##Defining and Using Single-Argument Methods

Since you can’t define your own general methods, but you can define lambda expressions

val dimmer_group_send_stop = [ GroupItem dimmer_group |
	dimmer_group.members.forEach [ dimmer |
		sendCommand(dimmer, STOP)
	]
]

works quite nicely to be able to define a single-parameter method/function that has the “easy” scope of the rules file. I might be able define it externally and do some crazy gymnastics to get to it through its auto-generated class, but I just want to get things working for now.

Yes, it works just fine in a rule like

rule "Test_Pico_Button_Up_Down_released"
when
	Item pico_Test_Pico_up changed from ON to OFF or
	Item pico_Test_Pico_down changed from ON to OFF
then
	dimmer_group_send_stop.apply(group_Kitchen_dimmers)
end

Defining and Failing With Two-Argument Methods

Map Dimmer to Value

Since the MAP and other transformation approaches in openHAB seem focused around strings, and dimmers need a number for the value, looking to something simpler was in order. Yes, I could define an Item for every dimmer, for every scene, and set them to the level for the scene, oh, yeah, need a UI for that… Are you crazy? That’s an insane amount of work for little gain. I just want to stuff the levels into a file and use them, at least for now. Once I get past trying to get the basic keypad functionality working half as well as the Lutron app provides, then I go to the binding and fix it so that it can activate the scenes managed by the hub itself, rather than duplicating it again in openHAB.

import java.util.LinkedHashMap

val LinkedHashMap<String, Number> scene_dim_bedroom_map =
	newLinkedHashMap(
		'caseta_dimmer_Bedroom_East_Cans' -> 30,
		'caseta_dimmer_Bedroom_West_Spots' -> 0,
		'caseta_dimmer_Bedroom_South_Cans' -> 30,
		'caseta_dimmer_Bedroom_Closet_Spots' -> 30
	)

Lots of ways to define it. Dimmer.name is unique, and, at least the way I have set them, readable and understandable. Lots of syntax options as well, but the Java-like, explicit definition helps remove ambiguity over something like

val scene_dim_bedroom_map =
	newLinkedHashMap(
		'caseta_dimmer_Bedroom_East_Cans' -> 30 as Number,
		'caseta_dimmer_Bedroom_West_Spots' -> 0,
		'caseta_dimmer_Bedroom_South_Cans' -> 30,
		'caseta_dimmer_Bedroom_Closet_Spots' -> 30
        )

or the unruly approach of not declaring the value type and hoping it can be deduced and cast properly.

Now, that works nicely in a rule action like

then
        group_Bedroom_dimmers.members.forEach [ dimmer |
                val String dimmer_name = dimmer.name
                val Number dimmer_level = scene_dim_bedroom.get(dimmer_name)
                if ( dimmer_level != null) {
                        sendCommand(dimmer, dimmer_level)
                }
        ]

(Before you jump on me, yes. I’m aware of allMembers but I’m interested in solving the core of the problem, not in tweaking my code to perfection for another of those “Design Pattern” postings at this point.)

Now, to generify that, so that it can be used for the ten or more times I need a button to set a scene

val dimmer_group_set_to_preset = [ GroupItem dimmer_group, LinkedHashMap<String, Number> preset_map |
	dimmer_group.members.forEach [ dimmer |
		val String dimmer_name = dimmer.name
		val Number dimmer_level = preset_map.get(dimmer_name)
		if ( dimmer_level != null) {
			sendCommand(dimmer, dimmer_level)
		}
	]
]

This, however fails at runtime in what should be effectively the same rule action

then
        dimmer_group_set_to_preset( group_Bedroom_dimmers.members, scene_dim_bedroom)

Adding logging shows that the preset_map is null on entry to the Procedure.

Designer gives apply() as the suggestion, with the proper argument types, doesn’t indicate an error, and does show information to suggest that it would work as expected.

Hovering dimmer_group_set_to_preset

Procedure2<GroupItem, LinkedHashMap<String, Number>> PicoRules.dimmer_group_set_to_preset

Hovering dimmer_group_set_to_preset.apply

void Procedure2.apply(GroupItem arg0, LinkedHashMap<String, Number> arg1)

Yet when it runs, the second argument is seen as null by the unmodified code, as well as the second logging statement in

val dimmer_group_set_to_preset = [ GroupItem dimmer_group, LinkedHashMap<String, Number> preset_map |
	logInfo("DimmerGroupPreset", "Entry with dimmer_group '{}'",
				     dimmer_group.toString)
	logInfo("DimmerGroupPreset", "Entry with preset_map '{}'",
				     preset_map.toString)

(Yeah, I know, .toString can’t be accessed from null)

So What Gives?

Why isn’t the second argument being picked up by the apply call and delivered to the code’s execution environment?

Most people use the ‘Functions’ methods outlined here


within Rules. Remember the rules run a DSL, not java

Thanks Ross (with apologies if that isn’t the right name!)

I’m painfully aware that the scripting language isn’t Java and that it appears to be within the confines of a class definition of sub-dialect of Xtend.

I did try fully qualifying the two-argument Procedure (it’s not a Function as it doesn’t have a return value) and didn’t get any different results.
(Note that the $ syntax has changed since Xtend 2.4.1 according to both https://www.eclipse.org/xtend/documentation/203_xtend_expressions.html#xtend-expressions-literals and the corrections done by Designer)

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

val org.eclipse.xtext.xbase.lib.Procedures.Procedure2<GroupItem, LinkedHashMap<String, Number>> dimmer_group_set_to_preset = [ GroupItem dimmer_group, LinkedHashMap<String, Number> preset_map |

The Procedure2 import is flagged by Designer as unused.

Trying to define it as a Function (and adding a return value) has the same behavior

val Functions.Function2<GroupItem, LinkedHashMap<String, Number>, void> dimmer_group_set_to_preset = [ GroupItem dimmer_group, LinkedHashMap<String, Number> preset_map |

(Designer collapses the full path to Functions)

Why not copy a simple working Function example,prove that it works. Then change things to your way and see which bit is the problem. I haven’t seen void appear in working examples, for an instance.

Been through that already, thanks though. Much of the mumbo-jumbo related to OH1.x has changed due to the new platform.

For example

val print2 = [ GroupItem dimmer_group, Number x |
        logInfo("print2", "arg0: '{}'\narg1: '{}'",
                          dimmer_group.toString, x.toString)
]

is recognized by Designer as a Procedure2 (as logInfo has a void return type) and works just fine. As long as the argument types and return type are clear to the evaluation environment, it appears to select the proper “flavor” of Procedure/Function, certainly in Designer, and likely at run time.

Guessing from the last and now-revised first post, you are now happy about two-argument procedures?

Testing the new ‘globals’ complaint in OH1 is interesting … it doesn’t work either, but there is a runtime error message in console log

var Number xxx = 1
var Number yyy = xxx

message -
2017-03-20 20:59:19.453 [WARN ] [m.r.i.engine.RuleContextHelper] - Variable 'yyy'
 on rule file 'general.rules' cannot be initialized with value 'xxx': An error occured during the script execution: The name 'xxx' cannot be resolved to an item or type.