How to annotate a state in a HashMap

I am working on a rule which updates item states using a hashmap

rule "Prozesse ausführen bei OpenHAB Start"
when
    System reached start level 100
then
    val String RuleName = Dateiname + ": \"Prozesse ausführen bei OpenHAB Start\""

    logInfo("Rule triggered", RuleName)

	val HashMap<String, String> itemsToSet = newHashMap(
		"Multimedia_Badezimmer_Echo_Standardlautstaerke" -> "25",
		"Multimedia_Kueche_Echo_Standardlautstaerke" -> "25",
		"Multimedia_Kueche_Echo_Soundsystem_Standardlautstaerke" -> "60",
        "Steckdosen_Kueche_Eierkocher" -> "OFF"
    )

    itemsToSet.forEach[ String itemName, value |
        val item = ScriptServiceUtil.getItemRegistry.getItem(itemName)
        if (item.state.toString != value) {
            item.sendCommand(value)
        }
    ]
end

Currently I am defining the target values (25, 25, 60, OFF) as Strings, however I’d like them to be Intergers and ON/OFFs. How can I specify the values to be a generic state (similar to GenericItem) (–> replace the second String in val HashMap<String, String> itemsToSet)?

Thank you for your help! :slight_smile:

You can declare the map as Map<String, State> (or even Map<Item, State> if you would like). But to populated it with values you need to make sure to use objects that implement State, e.g., instead of just

"Multimedia_Badezimmer_Echo_Standardlautstaerke" => "25"

you need to use

"Multimedia_Badezimmer_Echo_Standardlautstaerke" => new DecimalType(25)

OnOffType is defined as an enum, which are included top-level in the rules DSL, so for them it’s enough to write ON or OFF (without quotes).

Assuming this is OH 4, this is a perfect use case for a Scene. A Scene is a special purpose rule that consists of a list of Items and what state you want those Items to be in.

From there you can create another rule to call this rule at the desired trigger or click on the code tab and add a trigger to the Scene manually.

Unfortuantely, one of the many things that Rules DSL does not support is the ablity to call another rule. So you’d want to use a UI rule or any of the other rules languages.

Thank you very much! For numbers this worked. However the ON/OFF does not work.

    val HashMap<String, State> itemsToSet = newHashMap(
        "Steckdosen_Kueche_Eierkocher" -> OFF
    )

    itemsToSet.forEach[ itemName, value |
        val item = ScriptServiceUtil.getItemRegistry.getItem(itemName)
        if (item.state != value) item.sendCommand(value)
    ]

leads to

failed: Could not invoke method: org.openhab.core.model.script.lib.NumberExtensions.operator_notEquals(org.openhab.core.types.Type,java.lang.Number) on instance: null

If I understand the error correctly it thinks that the value is a java.lang.Number, right? I guess it interprets the value of the OnOffType which is a number…?

Using a logger I can see that the state of Steckdosen_Kueche_Eierkocher is an OnOffType as expected:

logInfo("Rule triggered", RuleName + ": Item" + itemName + ", state: " + item.state)

Itemstati.rules: "Stati setzen": Steckdosen_Kueche_EierkocherOFF

I also tried to use new OnOffType(OFF), however this leads to

failed: An error occurred during the script execution: Cannot invoke "org.eclipse.xtext.common.types.JvmType.eIsProxy()" because "type" is null

Steckdosen_Kueche_Eierkocher is definied as Switch in the items.


Thank you Rich for pointing out the usage of scenes! That’s pretty nifty, however I try to stick to only using DSL at the moment (something I might reconsider later when I got some spare time on my hands)

I like to mention that Rules DSL is probably the least good rules option in OH these days. It doesn’t support the full OH API. Being able to call another rule is only one area where it’s deficient. It has a half assed type system which leads to so many troubles (including the problem you are experiencing right now, see below) and odd behaviors. And it lacks a lot of standard programming features.

If you are ever looking into possibly moving off of Rules DSL (which I highly recommend) you would do well not to wait. As we progress Rules DSL has less and less to recommend it and is falling further and further behind.

Note, I never recommend converting your existing rules to Rules DSL. Only that you start to learn a bit about the other options and do new development on something else. Blockly is a great choice if you are not already a programmer.

Now for the error…

No, quite the opposite. What that error is saying is that it wants a java.lang.Number but was unable to convert OFF into that. Or more likely it’s having trouble converting OFF to an org.openhab.core.types.Type. Why does it expect a Type and a Number? :man_shrugging: Why, if my hunch is right, is it failing to convert OFF to a Type? Sometimes Rules DSL just can’t figure out types on it’s own.

However, one thing does stand out to me. You’ve set the type of the HashMap as <String, State>. But you cannnot sendCommand with a State. You can only sendCommand with a String or Command. OFFhappens to be both aStateand aCommandand in a normal language it would be able to figure that out and this would work. But Rules DSL only looks *up* the class hierarchy and never down and as far as it's concernted it has aStateObject. It never looks down to see it has anOnOffTypewhich is aCommandwhich it can use in asendCommand` method call.

Try changing the type from Stateto Command or, even better from a simplicity perspective, just use Strings.

val HashMap<String, String> itemsToSet = newHashMap(
        "Steckdosen_Kueche_Eierkocher" -> "OFF"
)

itemsToSet.forEachp itemName, value |
   val item = ScriptServiceUtil.getItemRegistry.getItem(itemName)
   if (item.state.toString != value) item.sendCommand(value)
]

For another example on the forum, in JS Scripting the above would look something like:

var itemsToSet = { 'Steckdosen_Kueche_Eierkocher': 'OFF' };

for(let itemName in itemsToSet) {
  let item = items[itemName];
  let cmd = itemsToSet[itemName];
  if(item.state != cmd) item.sendCommand(cmd);
}

But that’s just one possible approach. You could use Item Metadata to store the value with the Item instead of hard coding it into a rule. Then you can do something like this:

items.getItems().filter( i => i.getMetadata("initItem") !== null)
                .forEach( i => i.sendCommandIfDifferent(i.getMetadata("initItem").value)

The above gets all the Item with initItem metadata and commands the Item to the value of that metadata if the Item’s state is different from that value.

In a text based .items file you’d add initItem="OFF" to those Items you want to work with this code.