After much reading of various people's threads on optimizing this kind of a script I wrote 2 "working" solutions. One of them performed terribly but it helped me flesh out my plan. Special thanks to @pturmel. So many helpful threads on optimization.
I thought it would be good to include the source for what I ended up doing. Feel free to critique it if I'm doing something subpar. This is my first stab at doing something like this.
I have 2 python lists declared as globals to track changes in active alarm state. This allows me to detect alarm and warning states clearing. The rest is just querying the active alarms and throwing them in their respective buckets. So far it seems to be running great on a 1s timer script with 20 devices. I will load test it when I have more devices implemented.
# This routine does the following:
# Polls the active alarms and lists
# Compares to previous active alarms and list to determine what alarms and warnings have cleared
# Builds tag write lists to update all effected UDTs with a single tag write
# filtering out WWTP alarms because they don't follow the EMOS structure
props = [("alarmGroup", "!=", "WWTP")]
state = ["ActiveUnacked", "ActiveAcked"]
alarms = system.alarm.queryStatus(state = state, any_properties = props)
# active alarm and warning lists are compared to previous alarm and warning lists to see what alarm conditions need to be cleared
# the other 4 buckets are used to determine the current state of each device (block writes below)
retActiveAlarm = []
retActiveWarning = []
retAlarmUnacked = []
retAlarmAcked = []
retWarningUnacked = []
retWarningAcked = []
alarmList = alarms.getDataset()
# Sort the results into arrays of strings
for row in range(alarmList.getRowCount()):
rowString = alarmList.getValueAt(row, 1)
# The path is the same regardless of what bucket the alarm goes in so I'm doing the parsing in a temp var
path = rowString[rowString.find(":/tag:") + 6:rowString.find("/alm:") - 1]
path = '[TagProvider]' + path.rsplit("/")[0] + '/' + path.rsplit("/")[1]
# Warnings
if alarmList.getValueAt(row, 5) == 2:
retActiveWarning.append(path)
# Unacked
if alarmList.getValueAt(row, 4) == 2:
retWarningUnacked.append(path)
else:
retWarningAcked.append(path)
else: # Alarms
retActiveAlarm.append(path)
# Unacked
if alarmList.getValueAt(row, 4) == 2:
retAlarmUnacked.append(path)
else:
retAlarmAcked.append(path)
# Remove duplicates from lists (multiple alarms can be active per device)
retActiveAlarm = list(set(retActiveAlarm))
retActiveWarning = list(set(retActiveWarning))
retAlarmUnacked = list(set(retAlarmUnacked))
retAlarmAcked = list(set(retAlarmAcked))
retWarningUnacked = list(set(retWarningUnacked))
retWarningAcked = list(set(retWarningAcked))
tagPaths = []
values = []
# Build tagpaths and values for things currently in alarm. It's faster to write current state in a block than to check if it needs to change
for basePath in retAlarmUnacked:
tagPaths.append(basePath + '/Ind/Fault Unack')
tagPaths.append(basePath + '/Ind/Fault')
values.append(True)
values.append(True)
for basePath in retAlarmAcked:
tagPaths.append(basePath + '/Ind/Fault Unack')
values.append(False)
tagPaths.append(basePath + '/Ind/Fault')
values.append(True)
for basePath in retWarningUnacked:
tagPaths.append(basePath + '/Ind/Warning Unack')
tagPaths.append(basePath + '/Ind/Warning')
values.append(True)
values.append(True)
for basePath in retWarningAcked:
tagPaths.append(basePath + '/Ind/Warning Unack')
values.append(False)
tagPaths.append(basePath + '/Ind/Warning')
values.append(True)
# get the list of cleared alarms and warnings
clearedAlarms = [item for item in alarmScripts.gPrevAlarm if item not in retActiveAlarm]
clearedWarnings = [item for item in alarmScripts.gPrevWarning if item not in retActiveWarning]
# Build tagpaths and values for cleared alarms and warnings
for basePath in clearedAlarms:
tagPaths.append(basePath + '/Ind/Fault Unack')
tagPaths.append(basePath + '/Ind/Fault')
values.append(False)
values.append(False)
for basepath in clearedWarnings:
tagPaths.append(basePath + '/Ind/Warning Unack')
tagPaths.append(basePath + '/Ind/Warning')
values.append(False)
values.append(False)
# Update the previous alarms and warnings global with current alarms and warnings
alarmScripts.gPrevAlarm = retActiveAlarm
alarmScripts.gPrevWarning = retActiveWarning
system.tag.writeBlocking(tagPaths, values)
My global declarations look like this
global gPrevAlarm
global gPrevWarning
gPrevAlarm = []
gPrevWarning = []
I put an instance of my alarm state Indicator UDT a folder with other related UDTs for each device. The folder is named after the device. My UDT looks like this (some details removed for this example):
{
"name": "DeviceAlarmIndicator",
"parameters": {
"AlarmGroup": {
"dataType": "String"
}
},
"tagType": "UdtType",
"tags": [
{
"valueSource": "memory",
"expression": "{[.]Alarm Update}[1]",
"dataType": "Boolean",
"name": "Fault",
"value": false,
"tagType": "AtomicTag"
},
{
"valueSource": "memory",
"expression": "{[.]Alarm Update}[2]",
"dataType": "Boolean",
"name": "Warning Unack",
"value": false,
"tagType": "AtomicTag"
},
{
"valueSource": "memory",
"expression": "{[.]Alarm Update}[3]",
"dataType": "Boolean",
"name": "Warning",
"value": false,
"tagType": "AtomicTag"
},
{
"valueSource": "memory",
"dataType": "String",
"name": "Label",
"value": "TT",
"tagType": "AtomicTag"
},
{
"valueSource": "memory",
"expression": "{[.]Alarm Update}[0]",
"dataType": "Boolean",
"name": "Fault Unack",
"value": false,
"tagType": "AtomicTag"
}
]
}