Read value from variable where variable name is dynamically created

I am looking for a way to read a value from an existing variable in a DSL rule, where the name of the variable to read is generated dynamically by the rule. I have found plenty of discussions talking about dynamically generated item names, but could not find something comparable for variables.

So basically what I have is the following:
I have a number of items in an .items file which I want to act as constants for my system. Eg to determine default temperatures for heating / warm water:

Number WarmwasserTempNormal "Warmwasser Temperatur normal" (persistenceMap)
Number WarmwasserTempAbgesenkt "Warmwasser Temperatur abgesenkt" (persistenceMap)

Secondly I have a corresponding .rules file, in which I set the default values for those items. The idea is that on System started the default values are set for the above items. For better clarity and editability of the .rules file I first want to have a block with val declarations at the top of the file, eg

val Number WarmwasserTempNormal = 42
val Number WarmwasserTempAbgesenkt = 35

(not sure at this stage whether having the same names for the items and the variables is a good idea or even doable, but that is not really the topic here and can easily be fixed by adding pre- or suffixes to the variable names)

And then second, further down in the actual rule, I want to do something like WarmwasserTempNormal.postUpdate(WarmwasserTempNormal), ie post the value of the variable to the item.

So far so good, that should all work.

But as I am looking to have perhaps a dozen or more items to be set this way, I would not want to write a dozen times two lines for each item in the rules file, ie first the variable declaration and then second the postUpdate for that item. Instead, I was thinking to create a group for all these items, and then to use MyGroup.members.forEach[ i | <code> ] to cycle through each group item.

But what do I put for <code>? While every variable name can be deduced from the item name, something like i.postUpdate(i.name) will obviously not work here, as that will send the name string to the item, not the value of the variable of the same name.

Is there a function that will return a variable value from indirect reference? Eg something like readVar(i.name). Or some sort of equivalent to the eval() function in JavaScript that would allow me to do something like this:

var Number myValue = eval(i.name)
i.postUpdate(myValue)

Any help would be greatly appreciated.

Nope. The “last thing” defined as X will be what you get when refer to X. In this case, the global variable, so you’ll have no access at all to Item X in your rules file.

Well, technically you could work around that by getting a new Item reference with
val XX = ScriptServiceUtil.getItemRegistry.getItem("targetItemName")
but it’s all rather bothersome.

Going back to the intent and not the means, why not pre-populate a map object (“array”) with name-value pairs,

val Map<String,String> presets  = newHashMap(   // keyed by item name
	"ItemA" -> "apple",
	"ItemB" -> "3.142" 
)

A rule can for-each through that and postUpdate("name","state").

Don’t forget you can have persistence restore-on-startup Item states automatically.

When OH is restarting, why do you want to replace restored values from persistency database with values set by your startup rule?
Assuming that

WarmwasserTempNormal (persistenceMap)

are persisted items

For an example, persistence only restores valid states. Newly created Items stay NULL forever, unless you do something to them. A startup rule can check and set a default only if NULL found, which gets you over the first initialisation or other errors.

for this case I would simply set a state right after creating the item

Thank you, yes, that sounds like a good idea, let me have a go at that.
When I had been researching whether objects are an option (as that is how I would have tackled this in JS) I came across mentions of Maps, but not having a Java/Xtend background that did not immediately click (although JS does of course have them, as well). So thanks for pointing me in this direction!

Yes, but since you cannot give an item a default value, you will need eg a rule to set the item value at least once. Plus, this is going to become part of a more complex setup where I intend to couple the heating system with weather info, power usage info, and power generation from my PV system. And so this is going to require quite a bit of tweaking over an extended period of probably months. So I need to be able to adjust these default values easily.

Thinking about it, System started as a rule trigger will probably not suffice for that purpose, and so I will probably also create a virtual switch to be able to manually trigger rule execution.

When adding a new item simply launch the following URL in your browser:

http://openhab:8080/basicui/CMD?newItemName=newValue

(requires to have basicUI enabled. The official way would be to go to the API Explorer in MainUI and fire up a http/post command to that item)
and your new item has its starting value. from now on persistency does all the rest for you.

Fair enough, hadn’t occurred to me to go via the API, so thank you for pointing that out. But I believe in my particular use case that would be too cumbersome.

Use postUpdate("itemName", <state>) rather than item.postUpdate(<state>)


The example below should help…

Number Shower_Room_Towel_Rail_Boost_Button_Battery_Level "Shower Room Towel Rail Boost Button Battery Level [%.0f %%]" <battery> (g_Battery_Level) {channel="zwave:device:g24:node13:battery-level"}
Number Bathroom_Towel_Rail_Boost_Button_Battery_Level "Bathroom Towel Rail Boost Button Battery Level [%.0f %%]" <battery> (g_Battery_Level) {channel="zwave:device:g24:node14:battery-level"}

DateTime Shower_Room_Towel_Rail_Boost_Button_Last_Update "Shower Room Towel Rail Boost Button Last Update [%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <calendar>
DateTime Bathroom_Towel_Rail_Boost_Button_Last_Update "Bathroom Towel Rail Boost Button Last Update [%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <calendar>
rule "36: Battery Devices: Register last update time"
when
    Member of g_Battery_Level received update
then
    postUpdate(triggeringItem.name.replaceAll("Battery_Level","Last_Update"), new DateTimeType().toString)
end

^
Or for transferring the value of one item to another (e.g. on start up) …

rule "35: Various Items: Reset status on start up"
when
    System started
then
   g_Some_Group.members.forEach[ item | postUpdate(item.name.replaceAll("A", "B"), item.state.toString) ]
end

Yes, agree. In order to get the Map suggestion by rossko57 to work, I will have to use postUpdate("itemName", <state>), instead. Thanks for pointing that out.
I will not have time to take a stab at implementing this today, but will report back as soon as I have. Thanks again, all, for all the great help!

It works! Many thanks. Just for reference, here’s how I implemented it:

First defining the map

val KontrollzentrumDefaults = <String, String>newHashMap(
	"WarmwasserModusStandard" -> "1",
	"WarmwasserModusAus" -> "0", 
	"WarmwasserTempNormal" -> "42", 
	"WarmwasserTempAbgesenkt" -> "35"
)

and then in the rule iterating the map:

    KontrollzentrumDefaults.forEach[key, value, counter| 
        postUpdate(key, value)
    ]

1 Like

Seems like you should probably consider being able to set and adjust these Items from the UI with widgets. Coupled with restoreOnStartup you’ll have a way to give the Item it’s initial value after you create it and adjust each one later on. No need for a rule nor map nor anything like that.

You can manually run any rule by clicking the play button on that rule’s screen in MainUI. No need for a separate switch, unless you want to be able to run that rule from a widget too.

Note that is not officially supported and may cease to work at any point without warning.