List of All Historical Tags

New to Ignition so I apologize if this is an easy one...

I have an Ignition SCADA delivered from the vendor and want to get a full list of the tags used that are configured for Tag History. I can't seem to find a way to do this in the Designer as the tag export doesn't give the history parameters.

Is there a way to list all the tags that have "History Enabled" and ideally what the history parameters are? I have thousands of tags so going manually through the Tag Browser will take years.

If you upgrade to 8.1.19 or newer, you can use the Tag Report Tool:
https://docs.inductiveautomation.com/display/DOC81/Tag+Report+Tool

Thanks for the quick reply. Any chance this tool is available outside the designer (separate app), I'm stuck at version 8.1.2?

I think this gets all the tags with historian enabled, but there may be corner cases I didn't account for. It may choke on large tag trees, so use cautiously.

seedPath = '[default]'
def browseRecursive(path, results=[]):
	browseResults = system.tag.browse(
		path,
		{}
	).getResults()
	for i in browseResults:
		results.append(i)
		if i['hasChildren']:
			browseRecursive(i['fullPath'],results)
		
	return results

asdf = browseRecursive(seedPath)
allTheTuples = []
for i in asdf:
	try:
		for j in i['attributes']:
			allTheTuples.append((i['fullPath'],j.getId()))
	except:
		pass

for i in allTheTuples:
	if i[1] == 'history':
		print i[0]

Perhaps this version didn't have recursive browse that was actually usable (I can't remember when it was fixed), but this work around recursive browse was extremely slow (like using Windows file Explorer to search for files), so others finding this in later versions should not use it!.

Ignition Version 8.1.17
Tag tree: 5885 (including folders, UDT types, UDT sub-members, etc.)

This script took the following times over 9 runs:

Time (ms)
796
805
805
791
792
830
747
803
796
830
Average = 799.5

As @nminchin said, other versions may perform differently.

You can set seedPath to a smaller folder if you want to test performance before running against your whole tree. I always copy the tag tree to a development gateway before I do any work (like running this script), so long-running scripts, though annoying, aren't an existential threat to production.

800ms is huge for only 5000 tags. I ran it on 400k tags at one stage and it took many seconds from memory

1 Like

I agree that it's far from optimum. I wouldn't run it in a production setting, but it may be worth using as a one-off on a development gateway. For history specifically, you could probably do a similar thing with system.tag.getConfiguration and track where historyEnabled is present and true.

I ran the script, my Tag Provider has about 50000 tags and it took about 20 seconds.

It listed out all the tags but only their names - hoping to get details on history (enabled, deadband, tag group, etc.)

If you have access to the SQL tables you can select the tags from there, build a tag list and then do a system.tag.read and parse that out.

For only active tags you could do

SELECT [tagpath]
  FROM [sqlth_te]
  WHERE [retired] IS NULL
  ORDER BY tagpath

To get tags that have history but were retired

SELECT DISTINCT [tagpath]
  FROM [sqlth_te]
  ORDER BY tagpath

I did this originally but unfortunately the DB doesn't have the history config info.

You can use system.tag.getConfiguration in a script to get the configuration.

fwiw, this is what I use to find tags with history:

# Description:
#   Get list of historical tags from Ignition. Writes csv list into clipboard.

from java.lang.System import nanoTime as nt
t = [nt()]
tags = system.tag.browse(path="[default]", filter={"recursive": True, "valueSource": "opc", "tagType": "AtomicTag"})
t.append(nt())

# remove tags in UDT definitions
tags = [tag['fullPath'] for tag in tags if "_types_" not in str(tag['fullPath'])]

tagsHistEnTags = ['{}.historyEnabled'.format(tag) for tag in tags]
tagsHistEnTagVals = system.tag.readBlocking(tagsHistEnTags)
t.append(nt())

histTags = [tag for tag, en in zip(tags, tagsHistEnTagVals) if en.value == True]

print 'Tag count: ', len(histTags)

csv = '"TagPath"'
for tag in histTags:
	csv += '\r\n"{}"'.format(tag)

### REQUIRES MY shared.util.clipboard LIBRARY
shared.util.clipboard.writeText(csv)
print 'Time taken: ', (t[-1]-t[0])/1000000000.0, 's'

And, if you want to read a selection of properties from tags, I use this below. You could merge this into the script above to read all of the history properties of the tags found.

# Date: 2022-10-19
"""
Description:
	Finds tags and reads a list of properties for them. Returns a CSV of tagPaths and their property values into the clipboard.
"""
# SETUP
REMOVE_TAGS_IN_UDTS = True # set to True to remove tags found that are inside of UDT instances
path = '[default]' # the tag path to search for tags in
readProps = [] # the tag properties to read
readProps.append('opcItemPath')
readProps.append('enabled')
readProps.append('engUnit')
readProps.append('formatString')
readProps.append('scaleMode')
readProps.append('engLow')
readProps.append('engHigh')
readProps.append('rawLow')
readProps.append('rawHigh')
readProps.append('scaledLow')
readProps.append('scaledHigh')
######

tags = system.tag.browse(path = path, filter={'recursive': True, 'tagType': 'AtomicTag'})

if REMOVE_TAGS_IN_UDTS:
	udts = system.tag.browse(path=path, filter={'recursive': True, 'tagType': 'UdtInstance'})
	tagPaths = [str(tag['fullPath']) for tag in tags if not any(str(udt['fullPath']) in str(tag['fullPath']) for udt in udts)]
	print len(tags)-len(tagPaths), 'tags removed that were part of UDT instances.'
else:
	tagPaths = [str(tag['fullPath']) for tag in tags]

### filter the tag paths if required ###
#tagPaths = [path for path in tagPaths if 'Unit Parameters' in path]

print 'Number of tags: ', len(tagPaths)

allVals = [tagPaths]
for prop in readProps:
	paths = ['{}.{}'.format(tag, prop) for tag in tagPaths]
	vals = system.tag.readBlocking(paths)
	vals = [val.value for val in vals]
	allVals.append(vals)

csv = '"' + '","'.join(['tagPath'] + readProps) + '"\r\n'
csv += ''.join('"' + '","'.join(map(str, row)) + '"\r\n' for row in zip(*allVals))

### REQUIRES MY shared.util.clipboard LIBRARY
shared.util.clipboard.writeText(csv)
print 'Copied to clipboard.'
1 Like

Thanks for the above code, working with it now to suit my application but I get an error with running it with the clipboard line (InputError: No module named share)

My shared.util.clipboard library is needed which i've posted in other topics. just remove that if you don't want to copy to clipboard and just write to the console with print instead

1 Like

@nminchin

Just wanted to say thanks for the scripts - got everything working great now.

1 Like

You could initialize your list upon declaration:

readProps = [
    "opcItemPath",
    "enabled",
    ...
]

Or is there a reason you're using append ?
I find it more readable this way, maybe you don't ?

Maybe I'm strange, but I tend to prefer using append as it's then consistent how you add elements to the list if you were to add elements down further in the script. Instead of declaring it like you did and then swapping to using append later on :man_shrugging:

Either way obviously works though. PyCharm always has a whinge at my way of doing things and offers to convert it to your way, haha

Well I pretty much never use append, so here's that.

I should try PyCharm some day...