Counting Alarm Priorities in Power table according System Areas

Hello All,
I have a power table which displays the Alarms of Different Systems of a site and each system has four different alarm priorities. I want to count Each total number of alarms of each system according to priorities and move it to another power table. is there any script to do this?


MicrosoftTeams-image (13)

This is how we currently handle alarm summaries.

Call this from a gateway timer script:

def getAlarmSummaryAll():
		
	global alarmSummaryGlobal
	alarmSummary = {}
		
	state = ["ActiveUnacked", "ActiveAcked", "ClearUnacked"]
	alarms = system.alarm.queryStatus(state=state)
		
	for alarm in alarms:
			
		source = str(alarm.source)
		tagPath = str(alarm.source).split(':')[3]
		name = str(alarm.name)
		state = alarm.state.name()
		priority = alarm.priority.name()
		
		alarmSummary[source] = {'tagPath':tagPath, 'name':name, 'state':state, 'priority':priority}
			
	alarmSummaryGlobal = alarmSummary

I recommend placing this script in the project script as the one above. This script will get called from the UDT instance (which I describe below).

def getAlarmInstances(alarmPath):
		
	global alarmSummaryGlobal
	
	#Removes the tag provider is present
	if alarmPath.find(']') != -1:
		alarmPath = alarmPath.split(']')[1]
	
	alarmPriorities = {'Diagnostic':0, 'Low':1, 'Medium':2, 'High':3, 'Critical':4}
	statePriorities = {'ClearAcked':0, 'ClearUnacked':1, 'ActiveAcked':2, 'ActiveUnacked':3}
	alarmStates = {'ActiveUnacked':'flash', 'ActiveAcked':'static', 'ClearUnacked':'flash', 'ClearAcked':'static'}
	priorityCounts = {'Diagnostic': 0, 'Low': 0, 'Medium': 0, 'High': 0, 'Critical': 0}
	priority = None
	state = None
	styleClass = None
	hasUnackAlarm = 0
	resultDict = {'counts': priorityCounts, 'priority':	'', 'state': '', 'class': '', 'hasUnackAlarm': ''}	
	alarmPrioritiesList = []
	
	for alarm in alarmSummaryGlobal:
		
		#If alarm found within the summary then get counts and priorities
		#  The tagPath must match exactly starting from the left, this prevents partial matches
		tagPath = alarmSummaryGlobal[alarm]['tagPath']
		if tagPath.startswith(alarmPath, 0, len(alarmPath)):
			
			#Create a list of alarm priorities, so we can later find the highest priority and state for that priority
			alarmPriority = alarmSummaryGlobal[alarm]['priority']
			alarmState = alarmSummaryGlobal[alarm]['state']
			alarmPrioritiesList.append([alarmPriorities[alarmPriority], statePriorities[alarmState]])
			
			if alarmState == 'ClearedUnacked' or alarmState == 'ActiveUnacked':
				hasUnackAlarm = 1
			
			if alarmPriority == 'Diagnostic':
				priorityCounts['Diagnostic'] += 1
			if alarmPriority == 'Low':
				priorityCounts['Low'] += 1
			if alarmPriority == 'Medium':
				priorityCounts['Medium'] += 1
			if alarmPriority == 'High':
				priorityCounts['High'] += 1
			if alarmPriority == 'Critical':
				priorityCounts['Critical'] += 1
	
	if alarmPrioritiesList:		
		alarmListMax = max(alarmPrioritiesList, key=lambda item: (item[0], item[1]))
		priority = alarmPriorities.keys()[alarmPriorities.values().index(alarmListMax[0])]
		state = statePriorities.keys()[statePriorities.values().index(alarmListMax[1])]
		styleClass = alarmStates.get(str(state))
	
	#Take the results and place in a dictionary		
	resultDict = {
					'counts': priorityCounts, #A dicionary holding counts for each priority
					'priority':	str(priority), #The highest priority
					'state': str(state), #The most important state
					'class': styleClass, #The style class for indication purposes
					'hasUnackAlarm': hasUnackAlarm #At lease one unacknowledged alarm exists
					}	
	
	json = system.util.jsonEncode(resultDict)	
	return json	

Attached is the alarm UDT (tagAlarms.json). You will need to set the correct path within the element summary - replace question marks with your actual path to the script:

runScript('?????.getAlarmInstances',0,left({PathToParentFolder},lastIndexOf({PathToTag},{InstanceName})))

Now you can insert an instance of this UDT anywhere you want to get alarm summary information. You could use these instances for your table.

It should be noted that Iā€™m not the best with Python, so any pointers/tips are welcome. I have tested with 100s of alarms and 100s UDT instances and the gateway didnā€™t break a sweat. There are some elements that probably wonā€™t be needed for your application, but I left those in. You can remove them or simply ignore them.

tagAlarms.json (2.3 KB)

2 Likes

You will probably want to try to refactor to minimize excess queries to the alarming system. Currently that code will run 5 * the number of open clients you have, which will rapidly dramatically impact the gateway.
I would put a single queryStatus in a gateway timer or scheduled script at a given rate, and write out the results into separate tags accordingly, so that each client just has to read those tags to get the updated count.

3 Likes

@PGriffith made a great point. I updated my original post with something that may be more useful for you. Calling system.alarm.queryStatus a bunch can get expensive. The method I posted seems to be very lightweight. I havenā€™t seen any issues so far.

1 Like

Just for information, has the component ā€œAlarm Status Tableā€ the same performance impact of calling queryStatus at the ā€œRefresh Rateā€ of the component, or is the ā€œAlarm Status Tableā€ optimized in some way?

There was a performance issue with the ast in earlier version which was fixed, then came back again, and is now fixed in 8.1.19 I believe and now the ast should be good with performance

Since you asked... :wink:

			if alarmPriority == 'Diagnostic':
				priorityCounts['Diagnostic'] += 1
			if alarmPriority == 'Low':
				priorityCounts['Low'] += 1
			if alarmPriority == 'Medium':
				priorityCounts['Medium'] += 1
			if alarmPriority == 'High':
				priorityCounts['High'] += 1
			if alarmPriority == 'Critical':
				priorityCounts['Critical'] += 1

Can be reduced to:

			priorityCounts[alarmPriority] += 1
2 Likes

alarm.source has a getPathComponent method that you can use to get the tag path:

alarm.source.getPathComponent('tag')
1 Like

The impact on the gateway is effectively the same, but the alarm status table component has the advantage of automatically fetching the data in a background thread. Any scripting or expressions you create in Vision are going to run on the main Swing GUI thread (the EDT), making your application 'feel' less responsive if there are long running scripts. You have to jump through some hoops to move stuff to background threads in scripting, or otherwise take advantage of async mechanisms like bindings to make things more reactive.

Thank you for the reply.
I noticed that calling system.alarm.queryStatus from 10-15 clients requires a lot of resources of the gateway.

What do you think about creating a gateway script that writes a dataset memory tag with the status of the active/unacknwoledged alarms, and than subscribe that tag from the clients?
Do you think that this solution would require less resources on the gateway?
How big can be a dataset memory tag before becoming too expensive for the gateway/client?

I think he aleady said that .:wink:

1 Like

Almostā€¦
The question was about writing the alarm count for each area, so just few tags.

My question is:
instead of using queryStatus (or the Alarm Table) in each client, could it be less resource expensive on the gateway to use queryStatus only on the gateway, and transfer the information about each active/unacknowledged alarm (possibly hundreds/thousands of alarms) using a dataset memory tag (and then use a Table component to show the alarms on the client)?

Still applies, regardless of the post processing.

Yeah, itā€™s still generally going to be more efficient.

My arbitrary, seat of the pants number is that I wouldnā€™t put more than about 10,000 rows in a dataset tag. In this application, youā€™d be mostly reading, and care a lot less about the writing behavior, but just keep in mind that a memory tag will save all of its data into the internal database (so that itā€™s there when you restart the gateway) which can ā€˜clogā€™ your internal database.

This is the kind of thing that should be cached in a jython variable created at the top level of a script module.

Thank you for the reply, I hadnā€™t thought about the effect on the internal database of using a memory tag. Maybe I could post an idea in ideas.inductiveautomation.com to have non retentive memory tags, so as not to use the internal database.

But if I cache the result in a jython variable on the gateway, is there an efficient way to access it from the clients?

Use runScript or my objectScript to call the function that looks up and/or populates the cache.

objectScript("path.to.script.getThroughCache(args[0])", {path.to.cache.key})

This has become common enough for me lately that Iā€™ll probably create an expression function that optimizes this pattern. (Cache hit check in java only, invoke jython only on misses. Something like that.)

And if I use an expression tag, using a runScript call, is the internal database still used or is an expression tag non retentive?