Best way to read multiple tags and bind them to a table

Zone is the common part of your tag path, {0} is a place holder for .format(tag) so tag will be substituted into the place where {0} is. The value of tag will be the range from 1 to 10.

2 Likes

The expand a little on @dkhayes post, you can have multiple place holders too. Run this in a script console:

parentPath = '[default]Zone/'
tagPaths = [parentPath + 'Zone{0}'.format(tag) for tag in range(1,10)]

values = system.tag.readBlocking(tagPaths)

for tag in range(len(values)):
    print 'Zone{0}: {1}'.format(tag,values[tag].value)
1 Like

Thank you so much for the clarification. Now is it possible to get rid of zone0 here?
I changed the range and I am able to get the correct value associated with the zone name but I have zone0 without value this is because I do not have any zone0 tag.

image

This is all you have to do to get a two-column dataset that you can tie straight into a table data prop:

results = system.tag.browse(path = value, filter = {'tagType':'AtomicTag'})
myTags = []
for result in results:
	myTags.append({"Zone": result["name"], "value": result["value"]})
return myTags
1 Like

Very interesting!!! This was new to me. Thank you @lrose !!!

You should not be getting a Zone 0. range(1,10) will return iterate values from 1 to 9.

print range(1,10)

>>>
[1,2,3,4,5,6,7,8,9]
>>>
1 Like

Perhaps, but start putting system.tag.browse calls in multiple places and you will crush a perspective session or vision client.

Avoid potentially long running tasks on the UI, on top of that the added abstraction doesn’t really gain anything.

2 Likes

This is what I thought too, but with this code:

	parentPath = '[default]Zone/'
	tagPaths = [parentPath + 'Zone{0}'.format(tag) for tag in range(1,10)]
	
	values = system.tag.readBlocking(tagPaths)
	
	headers = ['Zone','Value']
	data = []
	for tag in range(len(values)):
	    data.append(['Zone{0}'.format(tag),values[tag].value])
	
	tableData = system.dataset.toDataSet(headers,data)
	self.getSibling("Table_0").props.data=tableData

I get this:

image

Ah, I see, small bug.

This:
data.append(['Zone{0}.format(tag), values[tag].value])

Should be:
data.append(['Zone{0}.format(tag + 1), values[tag].value)

1 Like

Amazing!!! Thank you!!! Could you please also update the solution yoo?

The original post has been updated.

1 Like

I have a similar task, where my tag folder consists of 1049 integers. The only difference is that I want to create a table with 3 columns. Column 1 will display the name of all tags that are 0, Column2 will display all tags name that are 1 and Column 3 all tag names that are 2. Step by step guidance would be highly appreciated as I’m fairly new to it. Thank you in advance

That doesn’t really work unless you will always have equal amounts of tags of each value. And since 1049 doesn’t divide evenly by 3, I’m guessing that’s not likely.

Pulling the tag name based on the value isn’t that difficult, but all rows must have 3 columns, you would have at least 1 row with only 1 tag name.

I suppose you could fill them with Null values or empty strings.

What exactly are you trying to accomplish?

Python’s map() function, when given multiple iterables of different lengths, will add None elements to the shorter ones. So, something like this in a script module:

# Use a script module top-level variable to hold the list of tagpath--don't use browse() every time
tagPaths = [.....]

def make012Dataset():
	values = [qv.value for qv in system.tag.readBlocking(tagPaths)]
	L0 = []
	L1 = []
	L2 = []
	for p, v in zip(tagPaths, values):
		name = p.split('/')[-1]
		if v == 0:
			L0.append(name)
		elif v == 1:
			L1.append(name)
		elif v == 2:
			L2.append(name)
	rows = map(None, L0, L1, L2)
	return system.dataset.toDataset(['Zeros', 'Ones', 'Twos'], rows)
1 Like

Here’s my unexperienced take on it, i was going to say that somebody will probably find a much more elegant way to do it, but @pturmel got here before i even got time to post :sweat_smile:.

Since you’ve asked for a step by step guide, here it is, based on my lengthy solution:

rootPath= 'Insert here the path to your folder with the tags inside'
tagPaths = [rootPath + '/Tag%d'%(tagNumber) for tagNumber in range(1050)]
#For every tag in the folder, we'll build the tag paths using the rootPath, plus
#the name of the tag and we'll use the %d inside the string to substitute it
#for the tagNumber, which will loop through all the numbers from 0 until it 
#stops at 1050
#This might be useless if your tag paths are named differently.

tagValues =  system.tag.readBlocking(tagPaths)
#Then, using the tagPaths list with all of the paths,
#we'll read all of them  with readBlocking
#This will return a list with qualified values, quality,timestamp and value.

column1 = []
column2 = []
column3 = []
#Each column is declared as a list variable, before we enter the loop since
#we can't append to a list if we don't declare it beforehand.

for path,value in zip(tagPaths,tagValues):
	tagName = path.split('/')[-1]
#We'll be looking through the list of paths and values,
# so we'll unpack each item of the list of qualified values from
# the result of readBlocking, use the .value part
# to compare and know which column to send it to.
#When we find the column, we'll grab the path that we also unpacked and
#send it to the list of paths for the correct column.
#The .zip function will combine both lists so it's easier to unpack each item.
#The split function will split the tag path string so we can just grab
#the last part using [-1] and we're left with only the tag name.
	if value.value == 0:
		column1.append(tagName)
	elif value.value == 1:
		column2.append(tagName)
	elif value.value == 2:
		column3.append(tagName)


columnsLengths = (len(column1),len(column2),len(column3))
smallestColumn = min(columnsLengths)
biggestColumn = max(columnsLengths)
#To build a dataset, we'll need a list of rows values for each column,
#so to avoid the error we would get if we tried to
#build a dataset with different column lengths, we add empty strings to
#the smallest lists until they all have the same lenght

for row in range(smallestColumn,max(columnsLengths)+1):
	if len(column1) < biggestColumn:
		column1.append('')
	if len(column2) < biggestColumn:
		column2.append('')
	if len(column3) < biggestColumn:
		column3.append('')
#Now, what's left is to joing all the contents of each column
#to build the rows using .zip,define what the name of each column will be
#and build the dataset.
rows = []
rows = zip(column1,column2,column3)
datasetHeaders = ['Column1','Column2','Column3']
dataset = system.dataset.toDataSet(datasetHeaders,rows)

#If you want to test, you can use the script on a onMouseClicked
#event handler script
#of a dataset and see the results using the following line:
event.source.data = dataset

If anyone reads through this, i would appreciate any comments about the explanation i’ve used,grammar and the code, i can already see a few things to improve, like the map function i forgot even existed.

1 Like

Pretty much the same as mine. Except you re-implemented the built-in python map() function so that you could use the built-in python zip function. Compare their documented behavior.

Other than that, note that I made a function. You did not. Functions in script modules can be called from runScript() bindings, which the OP will want to connect the result to a table, and keep it fresh. Script modules also provide a “cache” of top-level variables that can be treated as constants. Like the tag path list. No need to construct that every time.

4 Likes

Didn’t know that about Map(), good to know.

2 Likes

After trying it out and reading carefully the documentation it really makes sense now, thank you very much. I took this problem as a challenge and got to learn a lot.

2 Likes

So I have used the solution provided by @lrose and @pkhoshroo to accomplish a similar task of iterating over a series of tags to grab a state value.

I have a perspective table component data property bound to a query with return format of dataset to return a list of Job Numbers from a database seen below:

select job_number as JobNumber
from jobs

I then used a transform script to iterate over the tags that are associated with the job_number column like so (this is the part that matches the original solution to this topic that I used):

	parentPath = '[default]Job/'
	tagPaths = [parentPath + 'Job {0}/State'.format(tag) for tag in range(1,21)]
	
	values = system.tag.readBlocking(tagPaths)
	
	headers = ['Job Number','State']
	data = []
	for tag in range(len(values)):
	    data.append(['Job {0}'.format(tag + 1),values[tag].value])
	
	tableData = system.dataset.toDataSet(headers,data)
	self.getSibling("Table").props.data=tableData

The issue is while editing this script I can see in the table the returning additional column that I want. However, as soon as I click Apply, it errors out. I also have a null script at the bottom and do not understand why as it appears to work correctly.


image

A script transform must return a value.

Also, any time that you have a structure like this:

for tag in range(len(values)):

There is undoubtedly a better way to write it.

A transform is passed the value from the binding or any previous transforms. Typically that is modified and then returned. However, you don’t have to use the value, but you must return some object of one of the expected types.

Try this:

parentPath = '[default]Job/'
tagPaths = [parentPath + 'Job {0}/State'.format(tag) for tag in range(1,21)]

qValues = system.tag.readBlocking(tagPaths)

headers = ['Job Number', 'State']
data = [[job,state.value] for job,state in enumerate(qValues)]

return system.dataset.toDataSet(headers,data)
1 Like