String manipulation in rules

Hello Rule specialists,
how can I do string manipulation on item names within rules? Heres my example:
I have rollershutters and window contacts. A rule triggers a whole group of rollershuttes to go down.
In case the window is still open, I’d like to NOT close the rollershutter.

item:
String        eg_esszimmer_fenster    "Esszimmer Fenster [MAP(contact3.map):%s]"  
Rollershutter eg_esszimmer_rolladen   "Rolladen Essz. [%d %%]"

In bash I would do some string manipulation like this:

rollershutter = "eg_esszimmer_rolladen"
contact = ${rollershutter/_rolladen/_fenster}
echo $contact <- would spit: eg_esszimmer_fenster

I guess this is possible in the rule engine as well, right?
Any help is appreciated.
Thanks Seb

OH rules do not work like Bash. In OH 1 you cannot get the state of an item if all you have us it’s String name.

Easiest approach is to put the windows in a group and make the names similar to their corresponding rollershutter. In your rule do a foreach on the rollershutter group and then a filter on the windows group for the window that has the name that corresponds to the rollershuter. Something like:

rollershutters.members.foreach (rs |
    val windowName = // generate matching win name
    if(windows.members.filtet(w | w.name == windowName).head.state != OPEN)
        rs.sendCommand(CLOSED) // or what ever command you send)

I typed this on my phone so there are almost certainly errors in the above.

Hey Rich,
thanks for your reply.

I think this is the tricky part I’m trying to get my head around. You said that the devices should be named similar. How is that meant? Can you give an example for that? I was under the impression that my names are already similar.

Also would you mind explaining what your if clause does? You’re looking through the group of window contacts, if it matches the generated window name and checks for its state, right?

I think then my question is really how to generate the “val windowName”.
Thanks much

Sorry for my terse previous reply. I was typing it on my phone while trying to keep a toddler entertained. It is probably a miracle that it makes any kind of sense at all.

So I assumed that you had more than one window/rollershutter pair so you would want to make sure to name your items so you can easily split the name of the rollershutter apart and easily recreate the name of the corresponding window. For example:

Items:

Group gWindows
Group gRollershutters

String  eg_esszimmer-fenster-1 "Esszimmer Fenster 1 [MAP(contact3.map):%s]" (gWindows)
Rollershutter eg_esszimmer-rolladen-1 "Rolladen Essz. 1 [%d %%]" (gRollershutters)

String eg_esszimmer-fenster-2 "Esszimmer Fenster 2 [MAP(contact3.map):%s]" (gWindows)
Rollershutter eg_esszimmer-rolladen-2 "Rolladen Essz. 2 [%d %%]" (gRollershutters)

// any more pairs

Note that I added two groups and added the items to those groups so we can get at them in the rule.

That is correct. The gWindows.members.filter(w | w.name == windowName).head (NOTE the correction from “filtet” to “filter” and I renamed the group name to match the name I used above) creates a List that is populated with all of the members that match windowName and grabs the first one. Since all Items must have a unique name there should only be one and if there are none (i.e. the “.head” returns null) you have an error in generating windowName or in your Items file.

The .state gets the current state of the item and we are comparing that to OPEN and only evaluating to true and sending the command to close the rollershutter if the window is not OPEN.

Assuming you named your Items following some pattern like my example above, you can have a character that separates the first part of the name (the parts of the name that are shared between the window and the rollershutter, in the above the “eg_esszimmer”) from the part that tells you whether it is a window or rollershutter (in the above the “fenster” and “rolladen”) and the part that identifies the unique pair (in the above the “1” and “2”). I used a “-” to separate these parts.

So I would use the String.split method and use the parts to reconstruct the window name.

val nameParts = rs.name.split("-") // returns an array with three elements
val windowName = nameParts[0] + "-fenster-" + nameParts[2]

So the full code would be (with changes to names as listed above):

gRollershutters.members.forEach(rs |
    val nameParts = rs.name.split("-")
    val windowName = nameParts[0] + "-fenster-" + namedParts[2]
    if(gWindows.members.filter(w | w.name == windowName).head.state != OPEN){
        rs.postUpdate(DOWN)
    }
)
2 Likes

Hey Rich,
sorry for getting back so late on this. I know the problem with toddlers. I do have two of them over here. Thats also the reason why I have less time for my own hobbies like Openhab :smiley:

I want to thank for for such a detailed, well explained answer to my problem. The string.split helped me a lot and was the missing piece in my puzzle.
Thanks much, and really appreciate your help!
Seb

Hello Rich,

You you thing this would still work with openHAB2?

00:07:54.728 [WARN ] [del.core.internal.ModelRepositoryImpl] - Configuration model 'test.rules' has errors, therefore ignoring it: [142,9]: no viable alternative at input 'val'
[147,5]: extraneous input ')' expecting 'end'

The error refers to the line

val nameParts = rs.name.split("-")

Btw: There is a typo: “namedParts[2]” should probably mean “nameParts[2]”

Thanks a lot in advance!
Michael

Try replacing the parens around the lambdas with [ ]. That means the parens after forEach and after filter.

Hello Rich,

Thanks a lot for your quick reply. Yes, that was it. And for all others who wondering about still existing errors:
namedParts[0]” etc. does not work. You have to use “nameParts.get(0)” instead.
I read this in another post from you Rich - thanks a lot for your valuable posts in this forum that helped me a lot already!

Unfortunately, it seems to me a lot of guesswork and trial and error to figure out the syntax and possibilities of DSL. I would love to have a good and up-to-date documentation for this (not only me…).
For example: How do I even know that the .split() command exists without this forum?

Best regards
Michael

1 Like

The easiest is to use VSCode with the openHAB Extension. When you type

val nameParts = rs.

VSCode will give you a list of all the possible valid ways to complete the statement. Use the up and down arrows to scroll throgh the list or narrow the list down by start typing the name of a method.

image

And you have https://docs.openhab.org/configuration/rules-dsl.html and Xtend - Expressions and Java Platform SE 8.

It does take a bit of work understanding how the language works and knowing what you are dealing with. The Rules Domain Specific Langauge (DSL) is its own language (see the first link) that is based on the Xtext language and closely resembles the Xtend language (see the second link) which makes available all the core classes from the Java language (see the third link).

Rich,you suggested me to use VS-Code too, which I do, and it is not showing me this list of possible statements! I installed the openhab-extension in VSC by clicking Ctr+Shift+X, typing ‘openhab’ and click install and reload.
VS-Code gives me suggestions for already used vars or Items, but it is giving me a list only of vars or Items that I have already used in the same file, and even the once that are miss-spelled. From what I read about VS-Code installation for openhab, all configuration is optional and the deaults are with all the trick built in.

Should the suggestion-lists work with the current version of the openhab-extension? (I’m using 0.3.5) and the current version of VSC? (Version 1.20.0, Shell 1.7.9, Renderer 58.0.3029.110, Node 7.9.0, Architecture x64)

And should I do more configuration?

My OH-configfiles are on RaspberryPi with samba-share. My VSC runs on windows laptop.

Thanks! It helps a lot - as long as VScode can communicate with the openHAB language server / REST API.

Other people also struggling with getting a connection:
https://community.openhab.org/t/visual-studio-code-howto-connect-to-rest-api/36900/23

Ok, i got it working now. I had ‘http’ included in the hostname. Now I see lots of errors and warnings, so back to work!

I am looking, exactly for the same - window open → rollershutter stays open.

I used the code above, but it does not really went well. If I trigger this rule, I always get the message in log:

[ERROR] [untime.internal.engine.RuleEngineImpl] - Rule ‘Rolladen Wohnzimmer schliessen’: 3

I have more or less the same setup, except for items:

Group:Rollershutter:OR(UP,DOWN) gH_Rolladen_wz_switch “Rolladen Wohnzimmer”

which includes all items which are rollershutter.

The code is modified as follow:

rule “Rolladen Wohnzimmer schliessen”
when
Item EG_wz_switch_3_long received update OFF
then
gH_Rolladen_wz_switch.members.forEach[rs|
val nameParts = rs.name.split(“")
val windowName = nameParts.get(0) + "
” + nameParts.get(1) + “fk” + nameParts.get(3)
logInfo (“Fensterkontakt der offen is:”, +windowName)
if(gH_Contact_global.members.filter [fk | fk.name == windowName].head.state != OPEN){
postUpdate(rs,DOWN)
}]
end

I think, it is somehow in relation, that items comes not as a string value therefore the full rules does not fly (i.e. logInfo also do not appear in the log).

Any suggestions, what I could do to improve as I am not a real programmer? I understand, what the code intend to do basically (more or less), but I get really fast to my limit.

Any help is appreciated.

Thanks in advance.
Dom

No ideas?

val nameParts = rs.name.split("")

What is the character splitting the string “_” or “-” or what? There nothing in your ""

Also can you use the code fences, please? See:

Hello Vincent,

Thanks for your reply - sorry for my late reply, but I don’t get a notice about your answer.

I use “_” as a string seperator - I have no clue, why it is not shown in the code I posted.

Again, here is my code in code fences:

rule "Rolladen Wohnzimmer schliessen"
when 
    Item EG_wz_switch_3_long received update OFF
then
  gH_Rolladen_wz_switch.members.forEach[rs|
    var nameParts = rs.name.split("_")
    var windowName = nameParts.get(0) + "_" + nameParts.get(1) + "_fk_" + nameParts.get(3)
    logInfo ("Fensterkontakt der offen is:", +windowName)
        if(gH_Contact_global.members.filter [fk | fk.name == windowName].head.state != OPEN){
           postUpdate(rs,DOWN)
           }]
end

Thanks
Dominik

logInfo ("Fensterkontakt der offen ist:", windowName)

remove the “+”

if(gH_Contact_global.members.filter [fk | fk.name == windowName].head.state != OPEN){

Cannot refer to the non-final variable windowName inside a lambda expression

Thanks, but what do you mean with:

What to change? Sorry, my programming skills are really basic.

Thanks

What is the error message now?