Export configured alarms

Is there a way to export a list of configured alarms for tags? Thanks.

Use the tag report utility. You can filter the tags with alarms under additional properties. It generates a CSV file with the various fields you need like name, priority, etc.

3 Likes

Yes! You can export configured alarms for tags in Ignition by:

  1. Designer Export:

    • Open Designer β†’ Tag Browser β†’ select tags/folder β†’ Right-click β†’ Export β†’ XML/CSV.

    • Alarm settings are included in the export.

It’s against forum rules to post AI slop. Please don’t.

7 Likes

Who is this about?

My post is a reply to the post above that is a blatant AI post.

It's not that simple, the tag report tool will report tags that have alarms, but the format you get won't be that useful for a report or a spreadsheet. I'll dig something up tomorrow that does this and presents a nice format for a spreadsheet.

I’m interested to see your results, but you can manipulate the columns exported from the tag report tool, and it was perfectly fine and complete with a column of alarm names and priorities which is all I really needed for production to review.

This is the script I use. Note the very last function you likely won't have which copies the CSV to clipboard. Just comment it out and do with the CSV variable as you will

"""
Get the details of all alarms configured from a tag path and return a table to the clipboard.
Note: Uses system.tag.query to retrieve the list of tag paths along with their list of alarms. The values are read from the tags themselves

IMPORTANT NOTE:
It's highly recommended that the base tag path folder be "restarted" in the tag browser (right click on folder > "Restart Tag") before
running this so that all tag prop bindings are up-to-date and are evaluated if they haven't been evaluated previously. Otherwise you
may see nulls in these prop values.
"""
# these are the alarm props to read and include in the CSV
alarmPropNames = [
	'displayPath',
	'label',
	'name',
	'priority',
	'ackMode',
	'activePipeline'
]
baseTagPath = '*'
provider = 'default'
query = {
    "options": {
      "includeUdtDefinitions": False
    },
    "condition": {
      "path": baseTagPath,
      "attributes": {
        "values": [
          "alarm"
        ],
        "requireAll": True
      }
    },
    "returnProperties": [
      "alarms"
    ]
}

# get the tags not in UDTs - the only reason we need this is to know which tags are NOT in UDTs
query['options']['includeUdtMembers'] = False
alarmTagDetails_NonUDT = system.tag.query(provider, query)
# get all tags, including those inside of UDTs
query['options']['includeUdtMembers'] = True
alarmTagDetails = system.tag.query(provider, query)

# add a flag to identify if the tag is in a UDT or not - initially set all to True, then update later
for item in alarmTagDetails:
	item['inUdt'] = True

# we need a map to link fullPaths to their list indices
fullPathIndexLookup = {item['fullPath']: i for i, item in enumerate(alarmTagDetails)}
for item in alarmTagDetails_NonUDT:
	# overwrite the "inUdt" flag to False
	alarmTagDetails[fullPathIndexLookup[item['fullPath']]]['inUdt'] = False

alarmTagDetails = sorted(alarmTagDetails, key=lambda x: x['fullPath'])
readTagPaths = []
alarmTagPaths = [] # this list will contain the tagPath for each alarm in the same order as the `values` list created below
for alarmTag in alarmTagDetails:
	tagPath = str(alarmTag['fullPath'])
	
	for alarm in alarmTag['alarms']:
		alarmTagPath = '{}/alarms/{}'.format(tagPath, alarm['name'])
		alarmTagPaths.append({'tagPath': tagPath, 'inUdt': alarmTag['inUdt']})
		
		for propName in alarmPropNames:
			alarmPropTagPath = '{}.{}'.format(alarmTagPath, propName)
			readTagPaths.append(alarmPropTagPath)

values = [qv.value for qv in system.tag.readBlocking(readTagPaths)]

alarmDetails = []
#print len(values), len(alarmTagPaths)
for alarmIndex in xrange(0, len(values), len(alarmPropNames)):
#	print alarmTagPaths[alarmIndex / len(alarmPropNames)]['tagPath']
#	print alarmIndex, alarmIndex / len(alarmPropNames)
#	print alarmTagPaths[alarmIndex / len(alarmPropNames)]['tagPath'], '+', values[alarmIndex + alarmPropNames.index('name')]
#	print readTagPaths[alarmIndex + alarmPropNames.index('name')]
	alarmTagPath = str(alarmTagPaths[alarmIndex / len(alarmPropNames)]['tagPath']) + '/Alarms/' + values[alarmIndex + alarmPropNames.index('name')]
	a = {propName: values[alarmIndex + alarmPropNames.index(propName)] for propName in alarmPropNames}
	a['alarmTagPath'] = alarmTagPath
	a['inUdt'] = alarmTagPaths[alarmIndex / len(alarmPropNames)]['inUdt']
	alarmDetails.append(a)

labelOverrides = {
	'displayPath': 'DisplayPath [Area]',
	'label': 'Label (Description / Device Name)'
}

rows = ['"AlarmTagPath"\t"In UDT?"\t"' + '"\t"'.join(labelOverrides.get(propName, propName.title()) for propName in alarmPropNames) + '"']
for alarm in alarmDetails:
    row = '"{}"\t"{}"\t"{}"\t"'.format(
        alarm['alarmTagPath'], 
        "TRUE" if alarm['inUdt'] else "FALSE"
    )
    row += '"\t"'.join(str(alarm[propName]) for propName in alarmPropNames) + '"'
    rows.append(row)

csv = '\r\n'.join(rows)

shared.util.clipboard.writeText(csv)

Example output below. Note that tags with multiple alarms configured on them are presented as one alarm per row.

AlarmTagPath In UDT? DisplayPath [Area] Label (Description / Device Name) Name Priority Ackmode Activepipeline
[default]Site A/Alarms/Many Alarms/Alarms/Alarm FALSE Area 51 Alarm Label Alarm Low Manual
[default]Site A/Alarms/Many Alarms/Alarms/Alarm 1 FALSE Area 52 Alarm 1 Label Alarm 1 Low Manual
[default]Site A/Alarms/Many Alarms/Alarms/Alarm 2 FALSE Area 53 Alarm 2 Label Alarm 2 Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm TRUE None None Alarm Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm 1 TRUE None None Alarm 1 Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm 2 TRUE None None Alarm 2 Low Manual
[default]Site A/Test Device/Single Alarm/Alarms/Alarm TRUE None None Alarm Low Manual

Here's a version that provides the TypeId chain as well, i.e. it includes the TypeIds that the alarm is part of, and the chain if it's TypeIds are nested (e.g. Devices/PumpDOL -> Alarms/FancyAlarm, where the FancyAlarm type is nested within the PumpDOL type)

Get Alarm Details with TypeId context
"""
Get the details of all alarms configured from a tag path and return a table to the clipboard.
Note: Uses system.tag.query to retrieve the list of tag paths along with their list of alarms. The values are read from the tags themselves

IMPORTANT NOTE:
It's highly recommended that the base tag path folder be "restarted" in the tag browser (right click on folder > "Restart Tag") before
running this so that all tag prop bindings are up-to-date and are evaluated if they haven't been evaluated previously. Otherwise you
may see nulls in these prop values.
"""
# these are the alarm props to read and include in the CSV
alarmPropNames = [
	'displayPath',
	'label',
	'name', ## this is required ##
	'priority',
	'ackMode',
	'activePipeline'
]
# if you want to override any of the above prop names in the resultant CSV, do it below
labelOverrides = {
	'displayPath': 'DisplayPath [Area]',
	'label': 'Label (Description / Device Name)'
}
baseTagPath = '*'
provider = 'default'

### 1. Get the details of UDT Definition / Type Ids so we can add TypeId context to alarms ################################################
print "Step 1"
# get a list of all udt instances and their TypeIds so that we can identify what UDTs tags are coming from
queryTypes = {
  "options": {
	"includeUdtMembers": True,
	"includeUdtDefinitions": True
  },
  "condition": {
	"path": baseTagPath,
	"tagType": [
	  "UdtInstance"
	],
	"attributes": {
	  "values": [],
	  "requireAll": True
	}
  },
  "returnProperties": [
	"typeId"
  ]
}
results = system.tag.query(provider, queryTypes)
typesLookup = {str(t['fullPath']): t['typeId'] for t in results}

class TagPathTrie:
    """
    A trie (prefix tree) data structure for efficiently storing and matching hierarchical tag paths.
    
    This implementation uses path segments (split by '/') as trie nodes, enabling fast
    longest-prefix matching for hierarchical tagpaths.
    """
    
    def __init__(self):
        """Initialize an empty TagPathTrie with a root node."""
        self.root = {'children': {}, 'isEnd': False, 'key': None}
    
    def insert(self, path):
        """
        Insert a path into the segment-based trie.
        
        Args:
            path (str): A slash-separated hierarchical path (e.g., 'a/b/c').
        """
        segments = path.split('/')
        node = self.root
        
        for segment in segments:
            if segment not in node['children']:
                node['children'][segment] = {'children': {}, 'isEnd': False, 'key': None}
            node = node['children'][segment]
        
        node['isEnd'] = True
        node['key'] = path
    
    def findLongestPrefix(self, path):
        """
        Find the longest prefix match for a given path.
        
        Args:
            path (str): A slash-separated hierarchical path to search for.
        
        Returns:
            str or None: The longest matching prefix path stored in the trie.
        """
        segments = path.split('/')
        node = self.root
        longestMatch = None
        
        for segment in segments:
            if segment not in node['children']:
                break
            node = node['children'][segment]
            if node['isEnd']:
                longestMatch = node['key']
        
        return longestMatch

def buildTypesChains(tagDict):
	"""
	Build the chain of TypeId paths by traversing UDTInstance: TypeId pairs.
	
	Args:
		tagDict: Dictionary mapping tag paths to TypeIds
		
	Returns:
		Dictionary with same keys but hierarchical TypeId values
	"""
	result = {}
	
	for tagPath, typeId in tagDict.items():
		# Build list of TypeIds from root to current path
		typeChain = []
		
		# Start from current path and work backwards to root
		currentPath = tagPath
		while currentPath:
			if currentPath in tagDict:
				typeChain.insert(0, tagDict[currentPath])
			
			# Move to parent path (remove last segment)
			if '/' in currentPath:
				currentPath = currentPath.rsplit('/', 1)[0]
			else:
				break
		
		# Join the type chain with " -> "
		result[tagPath] = " -> ".join(typeChain)
	
	return result

# rebuild the typesLookup dict so that it contains a chain of TypeIds instead of just the last TypeId.
typesLookup = buildTypesChains(typesLookup)
# build a trie of udtInstance paths so we can quickly find the first UDT Instance path given a tag path
udtInstancesTrie = TagPathTrie()
for key in typesLookup.keys():
	udtInstancesTrie.insert(key)
### ================================================================================================================ ###

### 2. Query for alarms ################################################################################################
print "Step 2"
# query for the actual alarm tags
query = {
	"options": {
	  "includeUdtDefinitions": False,
	  "includeUdtMembers": True
	},
	"condition": {
	  "path": baseTagPath,
	  "attributes": {
		"values": [
		  "alarm"
		],
		"requireAll": True
	  }
	},
	"returnProperties": [
	  "alarms"
	]
}

# get all alarms tags
alarmTagDetails = system.tag.query(provider, query)
alarmTagDetails = sorted(alarmTagDetails, key=lambda x: x['fullPath'])

### 3. Read the alarm props required and build a list of details per alarm ###############################################
print "Step 3"
readTagPaths = []
alarmTagPaths = [] # this list will contain the tagPath and other info for each alarm in the same order as the `values` list created below
invalidAlarmTagPaths = []
for alarmTag in alarmTagDetails:
	tagPath = str(alarmTag['fullPath'])
	# get the path of the first UDT instance for the alarm tag, if the alarm is part of a UDT (returns None otherwise) and then get its TypeId chain
	udtInstanceKey = typesLookup.get(udtInstancesTrie.findLongestPrefix(tagPath))
	inUdt = True if udtInstanceKey else False
	
	for alarm in alarmTag['alarms']:
		alarmTagPath = '{}/alarms/{}'.format(tagPath, alarm['name'])
		if any(element in alarmTagPath for element in ('{', '}', ',')):
			invalidAlarmTagPaths.append(alarmTagPath)
		else:
			alarmTagPaths.append({'tagPath': tagPath, 'inUdt': inUdt, 'typeIdChain': udtInstanceKey})
			
			for propName in alarmPropNames:
				alarmPropTagPath = '{}.{}'.format(alarmTagPath, propName)
				readTagPaths.append(alarmPropTagPath)

values = [qv.value for qv in system.tag.readBlocking(readTagPaths)]


### 4. Collate the alarm details together into a single dict ################################################################
print "Step 4"
alarmDetails = []
for alarmIndex in xrange(0, len(values), len(alarmPropNames)):
	alarmTagPath = '{}/Alarms/{}'.format(alarmTagPaths[alarmIndex / len(alarmPropNames)]['tagPath'], values[alarmIndex + alarmPropNames.index('name')])
	a = {propName: values[alarmIndex + alarmPropNames.index(propName)] for propName in alarmPropNames}
	a['alarmTagPath'] = alarmTagPath
	a['inUdt'] = alarmTagPaths[alarmIndex / len(alarmPropNames)]['inUdt']
	a['typeIdChain'] = alarmTagPaths[alarmIndex / len(alarmPropNames)]['typeIdChain']
	alarmDetails.append(a)

### 5. Produce the CSV #######################################################################################################
print "Step 5"
#csv = '"AlarmTagPath"\t"In UDT?"\t"UDT Chain"\t"' + '"\t"'.join(labelOverrides.get(propName, propName.title()) for propName in alarmPropNames) + '"'
#for alarm in alarmDetails[:500]:
#	csv += '\r\n"{}"\t"{}"\t"{}"\t'.format(alarm['alarmTagPath'], "TRUE" if alarm['inUdt'] else "FALSE", alarm['typeIdChain'])
#	csv += '"' + '"\t"'.join(map(str, (alarm[propName] for propName in alarmPropNames))) + '"'
rows = ['"AlarmTagPath"\t"In UDT?"\t"UDT Chain"\t"' + '"\t"'.join(labelOverrides.get(propName, propName.title()) for propName in alarmPropNames) + '"']
for alarm in alarmDetails:
    row = '"{}"\t"{}"\t"{}"\t"'.format(
        alarm['alarmTagPath'], 
        "TRUE" if alarm['inUdt'] else "FALSE", 
        alarm['typeIdChain']
    )
    row += '"\t"'.join(str(alarm[propName]) for propName in alarmPropNames) + '"'
    rows.append(row)

csv = '\r\n'.join(rows)

shared.util.clipboard.writeText(csv)
if len(invalidAlarmTagPaths) > 0:
	print "Failed to retrieve alarm details for these alarms due to invalid tag paths:"
	for t in invalidAlarmTagPaths: print '\t', t

Example results:

AlarmTagPath In UDT? UDT Chain DisplayPath [Area] Label (Description / Device Name) Name Priority Ackmode Activepipeline
[default]Site A/Alarms/Many Alarms/Alarms/Alarm FALSE None Area 51 Alarm Label Alarm Low Manual
[default]Site A/Alarms/Many Alarms/Alarms/Alarm 1 FALSE None Area 52 Alarm 1 Label Alarm 1 Low Manual
[default]Site A/Alarms/Many Alarms/Alarms/Alarm 2 FALSE None Area 53 Alarm 2 Label Alarm 2 Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm TRUE DeviceTest None None Alarm Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm 1 TRUE DeviceTest None None Alarm 1 Low Manual
[default]Site A/Test Device/Multi Alarms/Alarms/Alarm 2 TRUE DeviceTest None None Alarm 2 Low Manual
[default]Site A/Test Device/Single Alarm/Alarms/Alarm TRUE DeviceTest None None Alarm Low Manual
[default]Site A/Test Device/New Instance/New Tag/Alarms/Alarm TRUE DeviceTest -> TestSubUDT None None Alarm Low Manual
4 Likes