Items in variables INSIDE brackets are not working

Hi, i have an automation which uses my blinds, my windowsensors and some buttons for the blinds. It is all in one rule. That rule works perfectly for my items.

I am defining my items as this:
<Binding>_<DeviceType>_<Position>_<Room>

Example for blinds:
Rollershutter Z2M_Rollladen_links_Wohnzimmer (Member of gRollladen)

Example for Buttons:
String Z2M_Rollladenschalter_links_Wohnzimmer (Member of gRollladenschalter)

Example for windowsensors:
Contact Z2M_Fenster_links_Wohnzimmer (Member of gFenster)

Since most of my rooms have more than one window, i used this to tell them apart. But now i encountered a problem. In my kitchen, and in my guest toilet, theres only one window each. So i changed the item syntax for them to this:
<Binding>_<DeviceType>_<Room>
Rollershutter Z2M_Rollladen_Kueche (Member of gRollladen)

As far as good, but now i am using a rule to determine which device triggered the rule and therefore rename some variables. I am using these variables throughout the rule to make it easier to read and understand for me. Each variable is an item which i can do .sendCommand, .state, .postUpdate with.

rule "Rollladen: Gesamte Steuerung"
when
    Member of gRolllaeden received command or
    Member of gRollladenschalter changed or
    Member of gMaximaltiefeRolllaeden received command or
    Member of gFenster changed
then
    var triggeringItemType = triggeringItemName.split('_').get(1)
    var maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    var rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    var fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))

    // do some stuff with it

Unfortunately this does not work with my new blinds in the kitchen and the guest toilet. Because it has no <Position> in its names. Therefore i changed the rule to this:

then
    if (triggeringItemName.split('_').length == 4) {
        var triggeringItemType = triggeringItemName.split('_').get(1)
        var maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
        var rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
        var fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    }
    else if (triggeringItemName.split('_').length == 3) {
        var triggeringItemType = triggeringItemName.split('_').get(1)
        var maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2))
        var rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2))
        var fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2))
    }

    // do some stuff with it

This rule should change the variables value, depending on the length of the triggeringItemName. Normally this should work, but i read that variables are only visible to its own block/brackets and its child blocks/brackets.

So using the variables outside the if-statements does not work. So i tried this:

then
    var triggeringItemType
    var maximaltiefeRollladen
    var rollladen
    var fenster
    if (triggeringItemName.split('_').length == 4) {
        triggeringItemType = triggeringItemName.split('_').get(1)
        maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
        rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
        fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    }
    else if (triggeringItemName.split('_').length == 3) {
        triggeringItemType = triggeringItemName.split('_').get(1)
        maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2))
        rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2))
        fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2))
    }

    // do some stuff with it

But this does not work either, because i think the variable gets a specification (int or something) outside of the blocks. And it cannot store an item-type into an int-variable (This is just guessing, i dont know much about programming languages)

So how do i fix my issue? Is there anything else i can do? Maybe .split('_').get(2) and anything similar is not a smart solution?

I know changing the items name is the easiest solution, but i am kind of strict with my “syntax”.

This is my whole rule, currently working only for
<Binding>_<DeviceType>_<Position>_<Room>

import org.openhab.core.model.script.ScriptServiceUtil // um .state von einer Variable abzurufen

rule "Rollladen: Gesamte Steuerung"
when
    Member of gRolllaeden received command or
    Member of gRollladenschalter changed or
    Member of gMaximaltiefeRolllaeden received command or
    Member of gFenster changed
then
    var triggeringItemType = triggeringItemName.split('_').get(1)
    var maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    var rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    var fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2) + "_" + triggeringItemName.split('_').get(3))
    var ankuendigung =  ScriptServiceUtil.getItemRegistry.getItem("Amazon_" + triggeringItemName.split('_').get(3) + "_Ankuendigung")

    if (fenster.state == OPEN) {
        if (triggeringItemType == "Rollladenschalter") {
            ankuendigung.sendCommand("Der Rollladen kann nicht bewegt werden, wenn das Fenster geöffnet ist.")
            return;
        }
        else if (triggeringItemType == "Rollladen" && receivedCommand != STOP && receivedCommand != 0) {
           rollladen.sendCommand(0)
           return;
        }
    }
    switch (triggeringItemType) {
        case "Rollladen" : {
            if (receivedCommand == DOWN) {
                if ((rollladen.state == maximaltiefeRollladen.state || rollladen.state > maximaltiefeRollladen.state) && rollladen.state != 100) { // Damit der Rollladen bei einer Abwärtsbewegung überhalb der Maximaltiefe nicht stoppt und neu anfährt
                    rollladen.sendCommand(STOP)
                    Thread::sleep(400)
                }
                rollladen.sendCommand(maximaltiefeRollladen.state as Number)
                return;
            }
            else if (receivedCommand > maximaltiefeRollladen.state) {
                rollladen.sendCommand(maximaltiefeRollladen.state as Number)
                return;
            }
        }
        case "Rollladenschalter" : {
            switch (newState) {
                case "open" : {
                    if (rollladen.changedSince(now.minusSeconds(3))) { // Wenn man davon ausgehen kann, dass sich der Rollladen aktuell bewegt
                        rollladen.sendCommand(STOP)
                    }
                    else {
                        rollladen.sendCommand(0)
                    }
                }
                case "close" : {
                    if (rollladen.changedSince(now.minusSeconds(3))) { // Wenn man davon ausgehen kann, dass sich der Rollladen aktuell bewegt
                        rollladen.sendCommand(STOP)
                    }
                    else {
                        if (rollladen.state == maximaltiefeRollladen.state && rollladen.state != 100) {
                            ankuendigung.sendCommand("Der Rollladen hat seine Maximaltiefe erreicht. Diese kann in der App angepasst werden.")
                        }
                        else if (rollladen.state == 100) {
                            ankuendigung.sendCommand("Der Rollladen hat seine Maximaltiefe erreicht.")
                        }
                        else {
                            rollladen.sendCommand(maximaltiefeRollladen.state as Number)
                        }  
                    }
                }
                case "stop" : {
                    rollladen.sendCommand(STOP)
                }
                case "" : {
                    return;
                }
            }
        }
        case "Maximaltiefe" : {
            if (fenster.state == OPEN) {
                sendBroadcastNotification("Die Maximaltiefe des Rollladens kann nicht angepasst werden, da das Fenster geöffnet ist.", "newwindow-open", "Maximaltiefe Rollladen")
                maximaltiefeRollladen.postUpdate(maximaltiefeRollladen.previousState(true).state as Number)
                return;
            }
            else {
                rollladen.sendCommand(receivedCommand as Number)
            }
        }
        case "Fenster" : {
            switch (newState) {
                case OPEN : {
                        rollladen.sendCommand(0)
                }
                case CLOSED : {
                    if (Tageszeit.state != "Tag" && previousState == OPEN) {
                        rollladen.sendCommand(DOWN)
                    }
                }
                //case "AJAR" : {
                //    
                //}
            }
        }
    }
end

Maybe consider not to write your rules against openHAB but with openHAB. :slight_smile:

  1. openHAB Items should be 100% independent of the underlying hardware. There is absolutely no need to refer to a specific binding.
  2. As you already organized the items in groups, why don’t you use the groups instead of the Item Registry?
    val strPosRoom = triggeringItem.name.split('_').get(2) + "_" + triggeringItem.name.split('_').get(3)
    val maximaltiefeRollladen = gMaximaltiefeRolllaeden.members.filter[i|i.name.endsWith(strPosRoom)].head
    val rollladen = gRolllaeden.members.filter[i|i.name.endsWith(strPosRoom)].head
    val fenster = gFenster.members.filter[i|i.name.endsWith(strPosRoom)].head

In question of your ‘positionless’ issue: maybe consider to write two underscores instead of one (so you get an “empty” position), this way split should not fail. If this does not work as intended, maybe set a “dummy” position (or, yes, rethink your name model…)

Thank you for your help.

I am mainly doing this, because it helps me understanding my devices a lot better when editing rules and such.

Easy answer: I did not know this function

This is a nice solution, thank you. As you might know, this does not work for Items without a Position. Can you think of an easy fix for my problem, without me renaming a lot of my devices and giving up my name model?

Adding a second underscore is not “sexy” enough for me. I know this is an easy fix but not what my crippling mind is looking for. I already tried a “dummy” position, but i could not think of any word i can fill it with to be satisfied.

I know it sounds pretty dumb to most of the people in here, but renaming my items or make them “ugly” (two underscores) should be the last option for me.

I do appreciate your help and the .members.filter will definitely find a place in my currently poor programming skill pool.

Well, another option is to .split(‘_’) and test via .size the amount of parts. This way you could determine if there are three parts or four. Something like this:

val strList = triggeringItem.name.split("_")
val strPosRoom = strList.get(2) + if(strList.size == 4) "_" + strList.get(3) else ""

Thank you, this is working perfectly. I did not know that you can add if-statements inside a variable-declaration. I thought of something similar but did not know ho to do so.

My rule now looks like this:

then
    var triggeringItemType = triggeringItemName.split('_').get(1)
    var maximaltiefeRollladen = ScriptServiceUtil.getItemRegistry.getItem("Rollladen_Maximaltiefe_" + triggeringItemName.split('_').get(2) + if (triggeringItemName.split('_').size == 4) "_" + triggeringItemName.split('_').get(3) else "")
    var rollladen = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Rollladen_" + triggeringItemName.split('_').get(2) + if (triggeringItemName.split('_').size == 4) "_" + triggeringItemName.split('_').get(3) else "")
    var fenster = ScriptServiceUtil.getItemRegistry.getItem("Z2M_Fenster_" + triggeringItemName.split('_').get(2) + if (triggeringItemName.split('_').size == 4) "_" + triggeringItemName.split('_').get(3) else "")
    var ankuendigung =  ScriptServiceUtil.getItemRegistry.getItem("Amazon_" + (if (triggeringItemName.split('_').size == 4) triggeringItemName.split('_').get(3) else triggeringItemName.split('_').get(2)) + "_Ankuendigung")

This is called a “ternary operator” and while most languages support them the syntax varies significantly from one to another. This give you (and future readers) the term to search for.

When/if you get to creating expressions in MainUI Widgets, you’ll use the JavaScript version of this operator:

(condition) ? if true : if false

Note that in ternary operator, both the if true and if false parts are a simple call to get a value or a calculation. Anything more complicated will require a full if/else statement.

Finally, another alternative for cases like this would be to define the variable with null or some other state and then use an if/else to populate it. This would be useful in cases where what needs to be done is too complicated to fit into a ternary operator.

This is what i tried before. The problem here was (i think) that i did not define the variables type. (Int or float or such) in the first place.
When populating it with an items-definition, my rules wont accept it because (i think) the variable had the wrong type in the first place.

Populating it with a registryItem, when the type is not an itembased-type in the first place, it wont work. Thats what i think, but i am not sure.

In Rules DSL, this is the only case where you need to define the type of the variable so you are correct. There’s not enough information in this one case for Rules DSL to figure out the type dynamically.

When working with an actual Item, if you are certain of the Item type you can set it to that: e.g. var SwitchItem mySwitchItem. If not, you can use GenericItem.

I’ll also note that in the other rules languages this sort of thing isn’t an issue. Those that are strongly typed require you to always define the variable type. Those that are fully weakly typed can handle this case even without defining the type. Rules DSL is kind of stuck half way in between. Rules DSL is weakly types except where it isn’t.

That’s one of the reasons I no longer recommend Rules DSL. Any advantages it once had for non-programmers have been far surpassed in Blockly and Rules DSL has never been a good choice for people who already know how to code (JS Scripting, jRuby, and Groovy etc. are better choices there).

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.