Best way to generate an alarm list

I would like to be able to generate a list of each tag that has enabled alarms and the alarm configuration for each of the enabled alarms. If anybody has written some script or query to do this cleanly, I would appreciate some help. Thanks!

results = system.alarm.queryStatus(source=["*"]) for alarm in results: print alarm.source

That should do the trick. Of course that just gives the source, but you could use the alarm object to grab whatever configuration settings you wanted out of it.

Using system.tag.browseTags(Simple) to get your tags and system.tag.getAlarmStates to get their alarm configuration can help.

Sorry all… but, I’ve tried to use the queryStatus above to get a list of all alarms, however I only get 27 alarms returned, where there should be hundreds. I ran this in two locations: my local dev environment, no connection to PLCs where I got the 27 alarms; I also ran it on the production gateway where it returned a large number of alarms. I’m now not confident the “full” alarm list that I gave the customer using this function is in fact a full list at all…
Why would these results be different for the same gateway backup, between two systems?
Is there a fool-proof way to extract out a full list of every single tag with an alarm attached to it? (regardless of whether or not it’s part of a UDT or not)

1 Like

Did you ever solve this? I need to present a list to customer as well...1000's of alarms across multiple tag providers.

I created a script using queryStatus and ran into issues where it wouldn't return the correct data. I believe it's a bug but IA really hasn't moved forward with it. My workaround was to restart all tags prior running the script. This seemed to work for me.

Thats disappointing to hear. Thats a pretty important need to document systems and get customer input to verbage and critcality of alarms and generating pipeline rules.

This is actually pretty simple with the new tag report tool.
This script is a basic report that grabs all configured alarms.

provider = 'default'
limit = 10000

query = {
  "options": {
    "includeUdtMembers": True,
    "includeUdtDefinitions": False
  },
  "condition": {
    "path": "*",
    "attributes": {
      "values": [
        "alarm"
      ],
      "requireAll": True
    }
  },
  "returnProperties": [
    "alarms",
    "tagType"
  ]
}
results = system.tag.query(provider, query, limit)

Note: simply using system.tag.query will return the alarms object for each tag and won't give you a nice CSV of all alarms you can use.

I use this script to retrieve all alarms and given prop values given a baseTagPath. It will also tell you if the tag is part of a UDT or not:

"""
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.

Known Limitations:
- alarm names with bindings in them are unsupported
"""

baseTagPath = '*'
provider = 'default'
# a list of alarm props to get values for
alarmPropNames = ['displayPath', 'label', 'name', 'priority', 'ackMode', 'activePipeline']
# the heading name to set the alarm props to, if you want to override it from the code name
csvHeadingNameOverrides = {
	'displayPath': 'DisplayPath [Area]',
	'label': 'Label (Description / Device Name)'
}

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 = []
for alarmIndex in xrange(0, len(values), len(alarmPropNames)):
	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)

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

shared.util.clipboard.writeText(csv)

It will produce something like this:

1 Like

Looks like a tag restart is still required based on your comment?

Yeah, definitely recommended, otherwise some alarm prop values may not be populated or correct

FYI, your script will barf if alarm names are bound to {TagName}.

Oh nice... I guess I've never done that before. That one's a bit of a pain to fix... I added it to the known limitations

1 Like

My alarm names are bound to {udt member}, so same issue?

I would be fine doing it manually using the tag report tool. But while it seems to grab all the alarms, I can't find a single viewable property that gives the alarm name. "Name" is empty.

That's why I used queryStatus because I think it handles stuff like that.

Ill try this, but the tag report tool itself didnt seem to give the alarm name under any report option, which is the most important property to capture. The property "name" returned the tagname suffix for all tags with alarms, but not individual alarms configured. Also, no method appears to delineate tags with more than one alarm configured to its value, like a high and low alarm.

you can configure the returned table by clicking the gear top right.
Add a row, select alarms and check preview.
Then rerun the search.
It returns all of the configured alarms and the parameters for them.

1 Like

Interesting...this script gives the alarm description that I need. Wonder why the tag report tool console does not, if the engine is the same.

Yes, I know, but none of those available properties gives the ALARM name.

Your tag query example returns the proper verbage under "name". The tag query tool only returns the tag's last path delimeter under "name".

Example:

Path = "HMI_Bits_3/2
Alarm Name = "CP-64-1101 loss of power"

Tag tool returns "2" as the name
Query returns "CP-64-1101 loss of power"

Edit...ah, I do see them under "Alarms", not names, as part of an extended data return. Not ideal, but useable. Thanks!!

1 Like