Dynamic Array/Dataset Tag to determine max value

I have a situation where I need to alter a list based on separate bool tags to determine max/min values. Ex. Tags: (PIT1, PIT2, PIT3) floats; (EN1, EN2, EN3) bools.
If EN1 and EN3 are true then add PIT1 and PIT3 into a list that is used with Max and Min function. I have tried adding rows to a dataset tag, which is triggered with a value change any of the EN tags, but it doesn't seem to work right. I get one tag to copy and thats it. Any suggestions? New path I can take?

Sounds like a race condition, where multiple EN bits are coming on in the same time frame and the last write to the dataset tag is winning.

Are the PIT tags' values constantly changing and you are trying to pull the value the entire time that the EN tag is on for that pit?

If you are allowed to add modules to your system, I would install Automation Pros' Integration Toolkit. Its completely free and adds a lot of functionality, including iteration in expressions.

This toolkit has a tags() expression function that accepts a list or dataset of tag paths and will poll that list at a set rate and return all the values of the tags in a single dataset. It accepts dynamically building the source tag list so it may do what you are looking for.

We are doing something similar to you but we are using it for monitoring voltages. We have a driving numeric value that tells an expression how many voltages we want to monitor and the expression builds the list of tag paths for the tags() function. We then look at the highest and the average of the returned dataset.

You would have 1 memory tag containing the paths to all the EN bits and their associated PIT tags, and 3 expression tags, all of which would be a dataset type. One expression tag would use tags to monitor all the values from the EN bits, one to build the list of value (PIT) tags to monitor and another that monitors all the selected PIT tags.

Your first tag would be a dataset with two string columns, "enableTagPath and valueTagPath".

"#NAMES"
"enableTagPath","valueTagPath"
"#TYPES"
"str","str"
"#ROWS","1"
"[provider]Path/To/Some/EN/Bit","[provider]Path/To/Some/PIT/Value"

Your second tag (1st expression tag) would have an expression of

unionAll(
	asMap("path", "str", "value", "b"),
	tags(
		unionAll(
			asMap("path", "str"),
			forEach(
				{Path/to/memory/tag},
				asMap("path", it()[0])
			)
		)
	)
)

Your third tag (second expression tag) would have an expression of

unionAll(
	asMap("path", "str"),
	forEach(
		where(
			forEach(
				{path/to/expression/tag/one},
				if(
					it()[1] = 1,
					lookup(
						{path/to/memory/tag},
						it()[0], Null,
						"enableTagPath",
						"valueTagPath"
						), Null
					)
				), !isNull(it())
			),
		asMap("path", it())
	)
)

Your final expression tag would have an expression of

unionAll(
	asMap("path", "str", "value", "f"),
	if(
		len({path/to/second/expression/tag}) > 0,
		tags({path/to/second/expression/tag}), asList()
	)
)

This would be the tag that you do all of your min/max/average calculations on.

I'm not sure exactly what this means, so a little explanation on what you're attempting to accomplish and what your true end goal is would be helpful.

If you can add the Integration Toolkit, then you should absolutely go that way. If not, then a script like this would build the list you're looking for.

tagPaths = ['PIT1','PIT2','PIT3','EN1','EN2','EN3']
qvList = system.tag.readBlocking(tagPaths)
floats = qvList[:len(qvList)/2]
enables = qvList[len(qvList)/2:]

minMaxList = [qv.value for i,qv in enumerate(floats) if enables[i].value]

EDIT: Corrected Missing syntax in script.

Yes, the values of the PIT are constantly changing. Thats the real importance to this. Ex. Batch 1 is run and PIT1-3 are used, so EN1-3 are true, then PIT1-3 will be monitored. By looking at the min and max of those PITs the operators know nothing is lagging behind. If PIT2 drops way down (noticed by a big difference between min and max) then the operators can save the batch, if caught in time.

Okay, in that case my suggestion using the Integration Toolkit will work, as it pulls in the values at a set rate.

If you aren't able to add that module, then you would need to set up a gateway timer script to execute the script from lrose at a set rate (1s or 5s would probably be fast enough).

Consider creating an expression tag that compares the delta between min and max to a setpoint or limit of some sort and has an alarm configured on it. That way you can make it more noticeable to the operator.

End goal is to analyze enabled instruments. Its a recipe situation, so each run has different instruments enabled. We need to make sure the instruments stay close in value, so operators watch min vs max values of enabled instruments.

I'm working on the script, from lrose, to see if I can see correct values and I'm getting "object is not iterable" error on the last line. Not sure. I assume its indicating the list is not iterable.

change that to

minMaxList = [qv.value for i,qv in enumerate(floats) if enables[i].value]

Edit: Added .value to enables[i] so it actually pulls the boolean value, not the qualified value object.

It was trying to iterate through a qualifiedValue object, which obviously does not allow this.

3 Likes

Oops. Corrected original script.

Thanks,

Great, thanks for working that out!

Thanks for the ideas. Its been very appreciated.

That's not quite right, fwiw. The tags() function accepts updates from the tag subscriptions it sets up, and holds them briefly so that other updates coming back-to-back will yield a single expression re-execution. That default is 5ms, but can be up to 1 second. If deliberately set to zero, then the tags are read without subscribing, and only when something else makes the expression execute again.

1 Like