Make rule with 196 if-statements more generic

Hi,

I am trying to integrate my alarm-system with OpenHAB. All the necesarry data is received via UDP in a string-variable called “Tex_UDP_Incoming”. Depending on the contents of that variable, other variables should be updated with a specific value.

My alarm system has 66 contacts (windows/doors) that can have the status “open”, “closed” or “tampered”. I have created a rule that manages three of these contacts. This is what I have so far:

rule "Update door/window contacts"

when   
    Item  Tex_UDP_Incoming received update
then
	if(Tex_UDP_Incoming.state.toString.contains("Z0160")) {
	    Tex_GV_Z016.postUpdate("0")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0161")) {
	    Tex_GV_Z016.postUpdate("1")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0162")) {
	    Tex_GV_Z016.postUpdate("2")
    }
	if(Tex_UDP_Incoming.state.toString.contains("Z0350")) {
	    Tex_GV_Z035.postUpdate("0")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0351")) {
	    Tex_GV_Z035.postUpdate("1")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0352")) {
	    Tex_GV_Z035.postUpdate("2")
    } 
	if(Tex_UDP_Incoming.state.toString.contains("Z0360")) {
	    Tex_GV_Z036.postUpdate("0")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0361")) {
	    Tex_GV_Z036.postUpdate("1")
	}
	if(Tex_UDP_Incoming.state.toString.contains("Z0362")) {
	    Tex_GV_Z036.postUpdate("2")
    } 
	else {
	    /* Do Nothing */
    }
end

These are my items:

/* Alarm interface */
String Tex_UDP_Incoming								"Incoming UDP String1 [%s]"						<settings>		(gSet)						{ udp="<[192.168.3.10:*:'REGEX((.*))']" }			

/* Zone Status */
Number Tex_GV_Z036										"Z036 - Bureau zijkant kipstand [MAP(windows.map):%s]"				<window> 		(gGV_Bureau, gAlarm)
Number Tex_GV_Z035										"Z035 - Bureau zijkant open [MAP(windows.map):%s]"					<window> 		(gGV_Bureau, gAlarm)
Number Tex_GV_Z016										"Z016 - Achterdeur [MAP(windows.map):%s]"							<door> 			(gGV_Keuken, gAlarm)

The rule is working fine. Values get updated correctly. However, if I expand my rule to include all contacts, I would get a rule with 198 if-statements (or 594 lines of code). I was hoping I could make the rule more generic.

I don’t know how to translate it into a rule, but I was thinking something like:

  • If Tex_UDP_Incoming contains a “Z” (for now, I don’t care about other incoming UDP packages)
    * Update the item where the 9th-10th-11th character (“016”) of the item name matches the 2nd-3rd-4th character of the value of Tex_UDP_Incoming (“016”) . This item should be updated with the 5th character of the value of Tex_UDP_Incoming (e.g. “0”).

Would that make sense?
If yes, any idea how I could translate this in to a rule?

Thanks!

Dries

I cannot type it out for your, but I would like to believe that the right REGEX transformation will allow you to parse the string as needed.
You seem to get all the info you need in a string with the first four digits your connected to your item name and the last one your command.
You can piece together item names from strings, this post here is imho an excellent guide:


REGEX can be tricky, but there are REGEX validators online (e.g., regex101) and the documentation hopefully gives you a start.
So in short:
creating a string with “Tex_GV_Z”+REGEX(here-comes-expression-for-getting-device-number converting it to a item object replace the string in your sendCommand with another REGEX that gives you the relevant digit, may just collapse your code into a more manageable size.
Sorry that I am unable to give more concrete examples but hopefully the pointers above will allow you to proceed.

    val inStr = Tex_UDB_Incoming.state.toString
    if(inStr.startsWith("Z")) {
        val itemName = "Tex_GV_"+inStr.substring(0, 3)
        postUpdate(itemName, inStr.substring(4))
    }

See

Since your names are all standardized I don’t see a need for REGEX if you stick to rules.

However, I’m not sure about how the TCP/UDP binding works, but if you can define more than one Item on the same socket you can drop the Tex_UDP_Incoming and the rule entirely and define each Item along the lines of:

Number Tex_GV_Z036 { udp="<[192.168.3.10:*:'REGEX(Z036[\d])']" }
Number Tex_GV_Z035 { udp="<[192.168.3.10:*:REGEX(Z035[\d])]" }

Note I’m not certain about that REGEX. It may need parens instead of brackets. The [\d] is supposed to match the last digit and becomes what the Item’s state gets set to. The first part will only match those packets that start with that String.

Rich

1 Like

Hi both,
Thank you for your input. With this information I’m sure I will be able to build an efficient rule.

I’ll post the final outcome.

@rlkoshak: regarding the following suggestion:

Number Tex_GV_Z036 { udp="<[192.168.3.10:*:'REGEX(Z036[\d])']" }

I tried it (also with some variants and parens instead of brackets), but I received binding errors. Also, when I check out other UDP item examples, it looks like you need one receiving item, but you can have multiple sending items. But your rule seems to be very clear.

One last practical question: the UDP string does not start with Z, but actually with "Z (so also the: “). But how can I define that character? inStr.startsWith(”“Z”) does not work.

Escape the quotes with a backslash.

"\"Z"

1 Like

Hi, your example works great!

Thanks a lot. Just amazing how I could avoid a rule with 600 lines of code with just one rule with 15 lines. :slight_smile:

The strange this is that, everytime the rule is executed, I get the following

2017-05-18 18:57:56.187 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Update door/window contacts': The argument 'state' must not be null or empty.

Even though the rule works fine.

These are (some of) my items:

/* Alarm interface */
String Tex_UDP_Incoming								"Incoming UDP String1 [%s]"						<settings>		(gSet)						{ udp="<[192.168.3.10:*:'REGEX((.*))']" }			

/* Zone Status */
Number Tex_Zone_Z001						"Z01 - Sabotage Sirene [MAP(windows.map):%s]" 												<error> 		(gAlarm)
Number Tex_Zone_Z002						"Z02 - Glasbreukdetector praktijkruimte [MAP(windows.map):%s]"								<error> 		(gKE_Praktijkruimte, gGlasbreuk)
Number Tex_Zone_Z003						"Z03 - Glasbreukdetector garage [MAP(windows.map):%s]"										<error>			(gGV_Garage, gGlasbreuk)
Number Tex_Zone_Z004						"Z04 - Glasbreukdetector berging [MAP(windows.map):%s]"										<error>			(gGV_Berging, gGlasbreuk)
Number Tex_Zone_Z005						"Z05 - Glasbreukdetector keuken [MAP(windows.map):%s]"										<error>			(gGV_Keuken, gGlasbreuk)
Number Tex_Zone_Z006						"Z06 - Glasbreukdetector living [MAP(windows.map):%s]"										<error>			(gGV_Living, gGlasbreuk)
Number Tex_Zone_Z007						"Z07 - Glasbreukdetector bureau [MAP(windows.map):%s]"										<error>			(gGV_Bureau, gGlasbreuk)
Number Tex_Zone_Z008						"Z08 - Glasbreukdetector nachthal [MAP(windows.map):%s]"									<error>			(gEV_Nachthal, gGlasbreuk)
Number Tex_Zone_Z009						"Z09 - Glasbreukdetector dressing [MAP(windows.map):%s]"									<error>			(gEV_Dressing, gGlasbreuk)
Number Tex_Zone_Z010						"Z10 - Glasbreukdetector badkamer [MAP(windows.map):%s]"									<error>			(gEV_Badkamer, gGlasbreuk)
Number Tex_Zone_Z011						"Z11 - Glasbreukdetector slaapkamer K&D [MAP(windows.map):%s]"								<error>			(gEV_MasterBedroom, gGlasbreuk)
Number Tex_Zone_Z012						"Z12 - Glasbreukdetector logeerkamer [MAP(windows.map):%s]"									<error>			(gEV_Logeerkamer, gGlasbreuk)
Number Tex_Zone_Z013						"Z13 - Glasbreukdetector ontspanningsruimte [MAP(windows.map):%s]"							<error>			(gEV_Ontspanningsruimte, gGlasbreuk)
Number Tex_Zone_Z014						"Z14 - Voordeur [MAP(windows.map):%s]"														<door>			(gGV_Inkomhal, gDeur)
Number Tex_Zone_Z015						"Z15 - Zijdeur [MAP(windows.map):%s]"														<door>			(gGV_Sas, gDeur)
Number Tex_Zone_Z016						"Z16 - Achterdeur [MAP(windows.map):%s]"													<door>			(gGV_Keuken, gDeur)
Number Tex_Zone_Z017						"Z17 - Raam garage rechts open/dicht [MAP(windows.map):%s]"									<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z018						"Z18 - Raam garage rechts kipstand [MAP(windows.map):%s]"									<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z019						"Z19 - Raam garage links voor open/dicht [MAP(windows.map):%s]"								<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z020						"Z20 - Raam garage links voor kipstand [MAP(windows.map):%s]"								<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z021						"Z21 - Raam garage links achter open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z022						"Z22 - Raam garage links achter kipstand [MAP(windows.map):%s]"								<window>		(gGV_Garage, gRaamcontact)
Number Tex_Zone_Z023						"Z23 - Raam berging open/dicht [MAP(windows.map):%s]"										<window>		(gGV_Berging, gRaamcontact)
Number Tex_Zone_Z024						"Z24 - Raam berging kipstand [MAP(windows.map):%s]"											<window>		(gGV_Berging, gRaamcontact)
Number Tex_Zone_Z025						"Z25 - Raam keuken zijkant links open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z026						"Z26 - Raam keuken zijkant links kipstand [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z027						"Z27 - Raam keuken zijkant rechts open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z028						"Z28 - Raam keuken zijkant rechts kipstand [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z029						"Z29 - Raam keuken achter rechts kipstand [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z030						"Z30 - Raam keuken achter rechts open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z031						"Z31 - Raam keuken achter midden open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z032						"Z32 - Raam keuken achter midden kipstand [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z033						"Z33 - Raam keuken achter links kipstand [MAP(windows.map):%s]"								<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z034						"Z34 - Raam keuken achter links open/dicht [MAP(windows.map):%s]"							<window>		(gGV_Keuken, gRaamcontact)
Number Tex_Zone_Z035						"Z35 - Raam bureau zijkant open/dicht [MAP(windows.map):%s]"								<window>		(gGV_Bureau, gRaamcontact)
Number Tex_Zone_Z036						"Z36 - Raam bureau zijkant kipstand [MAP(windows.map):%s]"									<window>		(gGV_Bureau, gRaamcontact)
Number Tex_Zone_Z037						"Z37 - Raam bureau voorkant open/dicht [MAP(windows.map):%s]"								<window>		(gGV_Bureau, gRaamcontact)
Number Tex_Zone_Z038						"Z38 - Raam bureau voorkant kipstand [MAP(windows.map):%s]"									<window>		(gGV_Bureau, gRaamcontact)
Number Tex_Zone_Z039						"Z39 - Raam nachthal open/dicht [MAP(windows.map):%s]"										<window>		(gEV_Nachthal, gRaamcontact)
Number Tex_Zone_Z040						"Z40 - Raam nachthal kipstand [MAP(windows.map):%s]"										<window>		(gEV_Nachthal, gRaamcontact)

An this is mine (your) rule:


rule "Update door/window contacts"

when   
    Item  Tex_UDP_Incoming received update
then
    val inStr = Tex_UDP_Incoming.state.toString
    if(inStr.startsWith("\"Z")) {
        val itemName = "Tex_Zone_"+inStr.substring(1, 5)
        postUpdate(itemName, inStr.substring(5))
    }
end

An example value that triggers the rule is: "Z0360
(including the ").

Try logging out the inStr.substring(5) and make sure it isn’t empty.

The error is coming from the postUpdate and it is complaining that the state argument (second argument) is an empty string.

Hi Rich,

Thanks again for your help. I have an idea of the problem.

The variable “inStr” (generated via Tex_UDP_Incoming.state.toString) has a lot of NULL values in it.

See openhab.log via Notepad++:

I tried to adjust the following line:

val inStr = Tex_UDP_Incoming.state.toString

To this line:

val inStr = Tex_UDP_Incoming.state.substring(6)

Assuming I would only have the first six characters for Tex_UDP_Incoming. However, this generates error messages. So I’m not doing it right:

2017-05-20 17:54:15.763 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Update door/window contacts': An error occured during the script execution: The name '<XMemberFeatureCallImplCustom>.substring(<XNumberLiteralImpl>)' cannot be resolved to an item or type.

Do you know how I can only put the first 6 characters in variable inStr?

Thanks,
Dries

Tex_UDP_Incoming.state.toString.substring(6)
1 Like

Thanks! Problem is now solved!!

rule "Update door/window contacts"

when   
    Item  Tex_UDP_Incoming received update
then
	val inStr = Tex_UDP_Incoming.state.toString.substring(0, 6)
    if(inStr.startsWith("\"Z")) {
        val itemName = "Tex_Zone_"+inStr.substring(1, 5)
        postUpdate(itemName, inStr.substring(5))
    }
end

I have found a better way to deal with NULL values, I use “trim”:

	val inStr = Tex_UDP_Receive.state.toString.trim

This removes all leading/trailing null values. So I don’t get the “The argument ‘state’ must not be null or empty.” error message anymore.