Best practice for modifying the value of an OPC tag

Problem description:

  • I am extracting data from an OPC-UA server that only supports read. I have no control over the format of the value from the OPC-UA server.
  • The format of the value is a variant array of size 2 (see example in picture). The only way I am able to not get a "TypeConversion" error in Ignition is to use datatype equal to string array for the OPC-tag.
  • My goal is to have a tag where the value is only the float inside the first element of the array, while the quality and timestamp is unchanged.

What I have tried to to:
By creating an Expression tag with the expression below, I am able to isolate the value that I am interested in:
toFloat(replace(replace({[default]tag_name[0]}, "Variant{value=", ""), "}", ""))

However, using the expression it also impacts the timestamp, thus I thereby losses the time of sampling of the sensor. It is also very cumbersome as I have to do this for about 1000 sensors.

Any tips on a scalable approach?

That looks to be a painful problem.

  • What's the brand of OPC-UA server?
  • What does the OPC Item Path look like?

Unfortunately, the replace() expression doesn't support regex. (The split() function does.)
A simpler expression that seems to work is
toFloat(substring({this.custom.variant}, 14))

You could create a UDT with two internal tags and a tagpath parameter. The first tag would get your Array[2] using the tagpath parameter and the second one would return the float.

This will be a bit ugly because Variant (and Variant array) is basically an unsupported type in Ignition. I think it'll be a little better in 8.3, but still not great.

Create a UDT. Create 3 tags:

  • OpcVariantArray
  • V0
  • V1

In the OpcVariantArray, add a value changed event script:

	import re
	from com.inductiveautomation.ignition.common.model.values import BasicQualifiedValue
	from com.inductiveautomation.ignition.common.model.values import QualityCode
	
	ts = currentValue.timestamp
	v0 = currentValue.value[0]
	v1 = currentValue.value[1]
	
	match0 = re.search(r'value=([^}]+)', v0)
	match1 = re.search(r'value=([^}]+)', v1)
	
	if match0:
		value = BasicQualifiedValue(match0.group(1), QualityCode.Good, ts)
		system.tag.writeBlocking(["[.]V0"], [value])

	if match1:
		value = BasicQualifiedValue(match1.group(1), QualityCode.Good, ts)
		system.tag.writeBlocking(["[.]V1"], [value])

you'll probably need to do some casting to int/float on the desired type on the values you pull out of the regex (match0.group(1) etc...) and generally clean this script up a but, but the basic idea is there...

Parameterize the UDT as needed so that you just instantiate 1000 of them or whatever.

3 Likes

Thanks for the approach, this works. Information to other beginners: