Arrays as Global Variables

Greetings! :slight_smile:
I’m running into a problem creating arrays and hoping someone can help.

I am running Openhab 2.3 on a Raspberry Pi.

This rule works. It is in it’s own rules file. (test.rules)

rule "Test Rule"
when
  Item Test_Trigger received update
then
  val contacts = newArrayList()
  contacts.add(newLinkedHashMap('name'->'Cal Evans', 'address'->'cal@example.com'))
  contacts.add(newLinkedHashMap('name'->'Bob Evans','address'->'bob@example'))

  logInfo('PING',contacts.get(1).get('name'))
  logInfo('PING',contacts.get(1).get('address'))
end

If I change test.rules to be the code below, it fails.

var contacts = newArrayList()
contacts.add(newLinkedHashMap('name'->'Cal Evans', 'address'->'cal@example.com'))
contacts.add(newLinkedHashMap('name'->'Bob Evans','address'->'bob@example'))


/*
 * Create a list
 */
rule "Test Rule"
when
  Item Test_Trigger received update
then
  logInfo('PING',contacts.get(1).get('name'))
  logInfo('PING',contacts.get(1).get('address'))
end

Saving test.rules gives me the following error in my log file.

[el.core.internal.ModelRepositoryImpl] - Configuration model 'test.rules' has errors, therefore ignoring it: [2,1]: missing EOF at 'contacts'

I am attempting to create a list of people and email addresses that can be used in the rules that are in the same rules file.

Anyone have any thoughts on what I am doing wrong?

TIA!
=C=

From my experience, in the ‘global’ context, you cannot manipulate variables
e.g.
var x = 1
x = x +1
will fail as well. I expect there’s correct technical terms to characterize all this that I don’t understand.

One workaround would be to populate your array from within a ‘System started’ rule.

Or there’s probably a way to write your global definition so that it includes the data all in one ‘line’ as well.

2 Likes

You can only do declarations at the top of the rules file. You cannot execute code.
You could populate the hashmap in the constructor instead.

1 Like

Thanks. That was the kick in the head I need to loosen things up. I am now experimenting with populating the array in another role. if this works, I’ll make that a System Started rule.

it’s not working 100% yet. I CAN populate the array in a separate rule and see it in my main test rule, however, when I try to access the array I am getting

 Rule 'Test Rule': 'get' is not a member of 'java.util.LinkedHashMap'; line 16, column 18, length 13

But hey, that’s a DIFFERENT error so…progress. :slight_smile:

Cheers! :slight_smile:
=C=

I have tried that and yes it works. It is (IMHO) inelegant. I would rather populate the array in a system started rule. However if I cannot get that to work (and currently I cannot) then yes, I will populate the array when I create it.

Thanks for the reply. :slight_smile:

=C=

The problem with that is you don’t know when that will be triggered during the startup. It maybe before items are defined, or some rules have already started running.
There will be a fix for that I think but loading your hashmap at the initialisation of the file is the best way to go.
You may think it is inelegant. I think it’s clean and efficient.

1 Like

That is a clear and simple way to put it!

1 Like

Update:
In the end, I opted for a group and each item in the group is a string. The label of the string is a comma delimited list “name, emailaddress”

Using a Group and Items gives me global access to it in all rules. Also, it is very easy to configure should I set an openHab up for a relative or neighbor. My goal was an easily configurable parametrized list.

Thanks to those who replied. :slight_smile:

Also, as always, thanks to everyone who works on OpenHab. I’ve tried 3 home automation platforms and OH is the the best one I’ve played with so far for my needs.

Cheers!
=C=

This is absolutely correct. There is no context at the global variables so global vars and vals cannot see each other nor can they be referenced. So if you can’t do it on one line then you have to do it in a Rule (System started Rule perhaps).

But in this case it is possible to do it all in one line.

val contacts = newArrayList(newLinkedHashMap('name'->'Cal Evans', 'address'->'cal@example.com'),
                                             newLinkedHashMap('name'->'Bob Evans', 'address'->'bob@example.com'))

But when I see something like this it starts to have just a whiff of code smell for Rules DSL. It’s a perfectly reasonable approach in most programming languages but for Rules DSL going down this path often leads towards long, complicated, and brittle code.

I was going to suggest something similar. I wrote up a DP for it awhile back: Design Pattern: Encoding and Accessing Values in Rules

Another approach for you to consider would be to use .map files and the transform Action.

For example, you could create a single map file:

Cal = Cal Evans$cal@example.com
Bob = Bob Evans$bob@example.com

If you are careful with your naming conventions for Items (see Design Pattern: Associated Items) you can then do something like:

rule "Cal needs an email"
when
    Member of CalsItems changes
then
    val info = transform("MAP", "addresses.map", "Cal").split("$")
    val name = info.get(0)
    val email = info.get(1)
end

Obviously you don’t provide enough detail to provide a more reasonable example. The advantage to using a .map file or files is that you don’t have to edit the Rule itself to add or remove names and addresses.

1 Like

Thank you Rich for taking the time to leave a comment.
(and thank you for your Design Patterns: Groups. That has been INVALUABLE to me. :slight_smile:

I see your point about .map files. I’m still a babe in the woods in OpenHab and haven’t gotten to the point of researching them yet. I may re-code once I get there.

Gonna have to read this a couple of times. I actually tried defining the variables outside of the rules and then populating them inside the rules but I kept getting an error in a different rule (same file) trying to access the data. Notify Rule has the details. After reading the DP, I’ll setup a new test. I like the way I am doing it now but I would like to get this idea to work for other situations.

Thanks for the input. :slight_smile:

Cheers!
=C=