How often is the tag path array expected to change? Every second? Every week? Only when a new area is requested for display?
Edit: Well, really screwed the pooch on my replies. Can only reply 3 consecutive times... editing this post to add #3:
Method 3: Utilize your dynamic tag paths array as-is. Add a fixed-size array of tag expression bindings, each pointing to their respective element in the tagPaths array:
root.custom.tagValues[0] = (Tag Expression Binding: {this.custom.tagPaths[0]} )
root.custom.tagValues[1] = (Tag Expression Binding: {this.custom.tagPaths[1]} )
..etc.
Any array elements beyond the tagPaths array size will throw an error. So. optionally, add an expression transform w/ coalesce({value},None) to each binding. Any tag paths that do not exist will result in a 'null' value (see far below).
In your 'overall status' property, write your complex expression based on max-tags possible (expression: max([0],[1],[2],...,[n]), or bind to tagValues property and execute script transform if complicated evaluation needed. Note 3: Not terribly performant, though perhaps a reasonable compromise. However, if maximum tagPath array size is not known / predictable, go ahead and throw this option out.
I was entertaining a few options, none of them terribly ideal, none answering your question directly, but perhaps with a workaround:
Method 1: Initial thought was to try dynamically assigning bindings via reading in the view.json file from the gateway, creating new bindings / updating the JSON, and writing the file back to the gateway. Note 1: If separate clients need different number of bindings on their respective views, or number of bindings needs to change at runtime, or if there's any chance you could mess up the file or filesystem, then go ahead and throw this option out.
(Edit: Separating methods out to separate posts...)
Method 2: Instead of dynamically updating a list of tag paths, would you be able to generate an array of all possible tag bindings that might exist, then have a dynamic array object which you could reference to 'pick' only the values you need? Note 2: This method is not very performant (script transform if any tag changes value - see posts above). If any tags are expected to change value frequently (every second) OR if tag names are not predictable / known, then go ahead and throw this option out: Example 2: Within custom properties of a View:
-- root.custom.tagValues[0] = (Direct Tag Binding to [default]Path/To/Tag0)
-- root.custom.tagValues[1] = (Direct Tag Binding to [default]Path/To/Tag1)
-- root.custom.tagValues[2] = (Direct Tag Binding to [default]Path/To/Tag2)
-- etc...
Then:
-- root.custom.tagSelect = Array object (binding resulting in dynamic array of indexes which match up with tagValues array elements).
--- root.custom.tagSelect [0] = 0
--- root.custom.tagSelect [1] = 2
Then:
Either use tagSelect array for visibility on a screen, or utilize an expression binding to perform your evaluation. Below is an example to filter the tagValues array based on selected tags.
-- root.custom.filtered = Expression structure binding w/ script transform:
--- tagValues = {this.custom.tagValues}
--- tagSelect = {this.custom.tagSelect}
---- Script Transform:
def transform(self, value, quality, timestamp):
filteredValues = []
for tagNum in value['tagSelect']:
filteredValues.append(value['tagValues'][tagNum])
return filteredValues
Hey Phil, can you explain this a little deeper? I'm not exactly following how a table could be used any different than specifying a predetermined array size.
I've been trying to find a better way to have a dynamic list of tags as well. So far the solution for the topic is the best I have seen, but curious how a table could benefit here.
If this is ever implemented, I'd make sure that you can read any property of the tags in the list, and not just the value. For example, we have the need to pass in a list of tag paths and read their CanWrite prop to disable a button if any are False.
I would love to have a true indirect OPC Tag function that will temporarily subscribe a list of tags.
Very useful for raw IO reads, arrays, etc from the PLC without having to create actual tags in the Ignition system.
We currently use a timer to refresh the tags and update the bindings on the form, but this is cumbersome.
Something like
valList = system.opc.subscribe([tagList],rate)
Into memory then it can be referenced by item?
You mean OPC Items (or nodes). While they may be "tags" on the other end, they are not "tags" in Ignition.
Sounds interesting, but probably difficult to do as a script function. An expression function similar to my tags() would have the context needed. Hmmm....
Yes OPC items, on older "other" hmi systems, it was quite simple to use true indirection on the opc item path of tags since the tags were local in memory of the client and you could just overwrite the item paths when you needed to read different OPC items from a PLC.
Listening for individual tag changes on a dynamic number of tags = hard (at least for my pee-brain).
However, if wanting only a snapshot of ALL tag paths at the same time, periodically, this feels reasonable now within Perspective - albeit, again using a script transform...
Something resembling:
A property (this.custom.opcPaths) containing an array (of any size) of strings of OPC paths (resembling 'ns=1;s=[PLC]TEST_TAG')
A second property containing an expression structure binding & script transform. Two items in the structure:
-- time: now(10000)
-- opcPaths: {this.custom.opcPaths}
Script transform might resemble:
Chris,
I have no issues with doing this with the opc.readvalues.
What I am talking with Phil about is what was called Indirect Addressing in other HMI systems. This allows opc items(nodes) to be subscribed and used in the HMI system without having to revert to using timers or transforms manually calling the readValues.
We have full database listings of OPC addresses for things like loops, devices, alarms, UDT structures, etc. Instead of having to create tags for OPC items that the operators will never use, but engineers and programmers will, we setup windows that pull the list of addresses, and then poll the PLCs for the data.
I understand. I agree, periodic polling of an OPC UA server is optimized by subscribing to those same items. Also, a 'timer' in the client as a trigger for polling a device feels dirty...
From an optimization standpoint, however, specifics of the driver probably matter here. If a "UA subscription" really just instructs a driver to periodically poll the end device (for example, a Logix device via IA's core driver -I believe- uses an explicit E/IP connection), then how much does that subscription buy you? I'd argue...not much...
Another option could be to build the tags anyway, except utilizing leased tag groups (slow scan = 5min / eternity, switch to fast scan when on-screen)?
I try to create a view with two parameters ,one is tagPath for input ,another is value for output , bind the tagPath to tag . In the view which I need get the value , I write a script bind the tagPaths array to create instances for flex repeat with parameters then I can get the values of tagPaths array,set flex repeat style to display none.