Identify itemname based on part of a name

Hi,

If have this piece of code:

    if(in_UDPmsg.startsWith("\"Z0")) {
        val itemName = "Tex_Zone_"+in_UDPmsg.substring(1, 5)
        postUpdate(itemName, in_UDPmsg.substring(5))
    }

When “in_UDPmsg” is updated with e.g. "Z0121, item Tex_Zone_012 is updated with value “1”.

This works fine.

However, in order to get another rule up and running, I need to enhance the naming convention of my items.
Items are currently called:

Tex_Zone_Z001
Tex_Zone_Z002
...
Tex_Zone_Z010
Tex_Zone_Z011
Tex_Zone_Z012

But I would need to add a 3-digit suffix. So the items could become something like:

Tex_Zone_Z001_101
Tex_Zone_Z002_100
...
Tex_Zone_Z010_051
Tex_Zone_Z011_050
Tex_Zone_Z012_080

Obviously, that would break the code I mentioned above. The only thing I can guarantee is:

  • The first 14 characters will always be unique (no two items that start with “Tex_Zone_Z012_”)
  • The last 3 characters will always be numbers with a fixed length (3 digits);

So is there a “find item name that contains…” option?

Thanks!

See my Assocaited Items Design Pattern.

Assuming that all your Items are a member of a Group then:

gTex_Zone.filter[sw|sw.name.contains("some string")

First, I might recommend coming up with a more meaningful naming pattern. Numbers make a lot of sense to computers but before long you will be scratching your head wondering “What light was Tex_Zone_Z025_102 again?” It is just as easy for OH and your Rules to deal with human meaningful names and then your code becomes more self-documenting.

There are lots of approaches to naming patterns and several threads on this forum on the topic. My latest approach is:

If the name starts with:

  • g - Group
  • v - sensor value or state
  • a - actuator
  • t - Expire based timer

The first word is the general functionality the Item addresses (e.g. Network, Lighting, etc). I’m not always diligent about this part and have a lot of legacy Items from before I adopted this convention.

I separate the parts of the name by _.

The last parts of the name can be anything depending on context.

So, for example, I have some code that generates an alert when certain remote sensors stop reporting. The relevant Items for one of those devices (named manticore in my DNS):

Group gManticore_SensorReporter (gSensorGroups)

Switch vNetwork_Manticore "Manticore Network [MAP(admin.map):%s]"
  <switch> 
  { channel="network:device:manticore:online", expire="1m" }

Switch vManticore_SensorReporter_Online "Manticore sensorReporter [MAP(admin.map):%s]"
    <network> (gSensorStatus)
    { mqtt="<[chimera:status/sensor-reporters:command:OFF:.*manticore sensorReporter is dead.*]", expire="10m,command=OFF" }    

String vManticore_SensorReporter_Uptime "Manticore sensorReporter Uptime [%s]"
    <clock> (gManticore_SensorReporter)
    { mqtt="<[chimera:status/manticore/heartbeat/string:state:default]" }

And then in my rules I can do things like:

// Given vManticore_SensorReporter_Uptime which receives a heartbeat I can:

// Get the parts of the name
var uptimeSplit = uptime.name.split("_")

// Get the Group:
var groupName = uptimeSplit.get(0).replace('v','g') + "_" + uptimeSplit.get(1)
gSensorGroups.members.filter[gr|gr.name == groupName

// Get the associated Online Item
var online = uptimeSplit.get(0) + "_" + uptimeSplit.get(1) + "_Online"

As long as the naming is consistent and easy to split apart and they are put into Groups it is relatively easy to pull associated items.

2 Likes

Hi Rich,

It is incredible how much you have helped me with sound advice. If it is not by directly answering my questions, it is by all your other posts (I’ve read already hours on this forum).

I have changed my code and it seems to work!

My new items:

Number Tex_Zone_Z035_40_1					"Z35 - Raam bureau zijkant open/dicht [MAP(tex_contacts.map):%s]"								<window>		(gGV_Bureau, gRaamcontact, gAlarmcontact)
Number Tex_Zone_Z036_40_2					"Z36 - Raam bureau zijkant kipstand [MAP(tex_contacts.map):%s]"									<window>		(gGV_Bureau, gRaamcontact)

My new rule (or part of it):

    // To translate UDP zone-updates (Z) to our individual zone-items.
    if(in_UDPmsg.startsWith("\"Z0")) {
    //    val itemName = "Tex_Zone_"+in_UDPmsg.substring(1, 5)
	val itemName = gAllecontacten.allMembers.filter[sw|sw.name.contains(in_UDPmsg.substring(1, 5))].head
	logInfo("Alarm","itemName =" + itemName) 
    postUpdate(itemName, in_UDPmsg.substring(5))
    }

The only thing I don’t understand is what I see in openhab.log. I would have expected one entry (since I have one “loginfo” statement). But I actually get this:

2017-05-25 11:42:10.831 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=0, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.839 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.847 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.850 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.858 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.861 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.861 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.863 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.864 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.874 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.876 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.879 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.883 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.886 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.887 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.890 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.893 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Alarm interface via UDP': null
2017-05-25 11:42:10.900 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.901 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.901 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.902 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.908 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.915 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.916 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.917 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
2017-05-25 11:42:10.923 [INFO ] [eclipse.smarthome.model.script.Alarm] - itemName =Tex_Zone_Z035_40_1 (Type=NumberItem, State=1, Label=Z35 - Raam bureau zijkant open/dicht, Category=window, Groups=[gGV_Bureau, gRaamcontact, gAlarmcontact])
  • The first line seems to contain the old value of the item (State=0) all lines after that contain the new value (State=1);
  • I also see a ScriptExecutionThread - Rule ‘Alarm interface via UDP’: null

Besides that, everything seems to be functional.

Related to your advice on naming convention: you are absolutely right. For all other items, I have a very clear naming convention, split by ‘_’ and understandable by humans. However, since I didn’t have the “contains” trick you just explained to my, I assumed that the names of these new items should follow a more rigid structure. I can now adjust the names to be back in line with my naming convention.

How is your rule triggered? If it is triggered by Item gAllcontacten received update then the rule gets triggered multiple times for each individual update to one of its members.

The reason why the old one appears to have the old state may have something to do with timing, though I usually see this problem when dealing with persistence, not just straight Item states.

What is your full rule?

Finally, the null line I see sometimes when a rule gets pounded like this handling lots of events all at once in parallel. I’ve not found a good solution for it short of rewriting the rule triggers to list each Item individually rather than using the Group or adding a ReentrantLock which prevents more than one copy of the rule from running at the same time.

Hi,
Below you can find the entire rule.
It is not triggered by Item gAllcontacten , but by another item. This rule is typically triggered every 3 seconds. However, it will only end up in the problematic if-statement (in_UDPmsg.startsWith("“Z0”)) a few times per day (less than 20 times).

Given the limited complexity of the rule, I would expect the rule to be executed in less than a second.

Thanks,
Dries

rule "Alarm interface via UDP"

when   
    Item  Tex_UDP_Receive received update
then
	// We will start with processing the Creston protocol compliant commands (zone updates, armed/disarmed updates and user updates). 
	// After that, we will try to derive some other information based on the information displayed on the Alarm Keypad.
    // We need to trim the incoming UDP package, since that sometimes contains trailing NULL characters.
	var in_UDPmsg = Tex_UDP_Receive.state.toString.trim

    // To translate UDP zone-updates (Z) to our individual zone-items.
    if(in_UDPmsg.startsWith("\"Z0")) {
    //    val itemName = "Tex_Zone_"+in_UDPmsg.substring(1, 5)
	val itemName = gRaamcontact.allMembers.filter[sw|sw.name.contains(in_UDPmsg.substring(1, 5))].head
    postUpdate(itemName, in_UDPmsg.substring(5))
    }
	
    // When alarm is Armed (A) we register who armed it.
	else if(in_UDPmsg.startsWith("\"A0")) {
        postUpdate(Tex_Alarm_Status, "1")
		postUpdate(Tex_Alarm_Status_Update, new DateTimeType())
		postUpdate(Tex_Alarm_Partition, in_UDPmsg.substring(2, 5))
		postUpdate(Tex_Alarm_ArmDisarmUser, in_UDPmsg.substring(5, in_UDPmsg.length))
    }

	// When alarm is Disarmed (D) we register who disarmed it.
	else if(in_UDPmsg.startsWith("\"D0")) {
        postUpdate(Tex_Alarm_Status, "0")
		postUpdate(Tex_Alarm_Status_Update, new DateTimeType())
		postUpdate(Tex_Alarm_Partition, in_UDPmsg.substring(2, 5))
		postUpdate(Tex_Alarm_ArmDisarmUser, in_UDPmsg.substring(5, in_UDPmsg.length))
	}

	// When a user is logged on to the keypad (U), we register the user ID.
	// This information is also captured in the armed/disarmed message. However, in theory a user could log on to the keypad and do something else than arm/disarm.
		else if(in_UDPmsg.startsWith("\"U0")) {
			postUpdate(Tex_Alarm_User, in_UDPmsg.substring(4, 5))	
	} 

	// We need to process the response to our ASTATUS call. If we receive a "NNNNNNNN", it means the alarm is not armed. If we receive a "YNNNNNN" the alarm is armed.
	// This is an optional step. Normally we should have captured this information via the armed/disarmed messages (A...)
		else if(in_UDPmsg.startsWith("\"NNNNN")) {
			// The alarm is not armed. 
			if (Tex_Alarm_Status.state == 1) {
				postUpdate(Tex_Alarm_Status, "0")
			} 
	} 
		else if(in_UDPmsg.startsWith("\"YNNNN")) {
			// The alarm is armed. 
			if (Tex_Alarm_Status.state == 0) {
				postUpdate(Tex_Alarm_Status, "1")
			// Also when the alarm is in exit mode, we should change the status.
			} else if (Tex_Alarm_Status.state == 2) {
				postUpdate(Tex_Alarm_Status, "1")
			}
	} 
	// Messages not related to zone/area/user updates are typically what is displayed on the alarm keypad. They do not adhere a fixed naming convention.
	// You will need to finetune this part to your specific alarm-configuration.
	else {
		// There is no separate UDP message that registers when the alarm has been triggered/activated. However, if the keypad lines contains "inbraak alarm", you have unwelcome visitors.
		if(in_UDPmsg.startsWith("\"inbraak alarm") && Tex_Alarm_Status.state != 3) {
			postUpdate(Tex_Alarm_Status, "3")
			postUpdate(Tex_Alarm_Status_Update, new DateTimeType())
		}
		// There is no separate UDP message that registers when the alarm is in exit mode. However, if the keypad lines contains "Partit.in Uitl.", the alarm is in exit mode.
		else if(in_UDPmsg.startsWith("\"Partit.in Uitl.") && Tex_Alarm_Status.state != 2) {
			postUpdate(Tex_Alarm_Status, "2")
			postUpdate(Tex_Alarm_Status_Update, new DateTimeType())
		}
		// There is no separate UDP message that registers when the exit mode has failed. However, if the keypad lines contains "wapening gefaald", the alarm has failed to arm after exit mode.
		else if(in_UDPmsg.startsWith("\"wapening gefaald") && Tex_Alarm_Status.state != 4) {
			postUpdate(Tex_Alarm_Status, "4")
			postUpdate(Tex_Alarm_Status_Update, new DateTimeType())
		}
	// The information displayed on the key pad needs to split back in two (as on the keypad itself).
	// Since the length of these messages are unpredicable (between 6 and 32 characters), we need to include some more if statements.
		if (in_UDPmsg.length < 17) {
			postUpdate(Tex_Alarm_Textline1, in_UDPmsg.substring(1, in_UDPmsg.length))
		} else if (in_UDPmsg.length > 16) {
			postUpdate(Tex_Alarm_Textline1, in_UDPmsg.substring(1, 17))
			postUpdate(Tex_Alarm_Textline2, in_UDPmsg.substring(17, in_UDPmsg.length))			
		}
	}
end

I’m not sure, but I believe a reboot has solved the ScriptExecutionThread-null-problem… :slight_smile: