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.

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