TLDR
General question: What is best practice for centrally defining logical functions which are heavily utilized, in the context of large scale systems?
Specific context: Will defining colour maps using centrally defined Python scripts be scalable for large systems?
Context
Hi all, I’m currently administrating and improving a medium sized system (on the order of 1,000 control devices and 30k tags) at a process plant, and working on improving the standards. I need to ensure any changes we make will be suitable for future large systems (10k+ control devices, 100k’s tags).
Our library has on the order of 30 device type templates, each of which has an associated popup (so say 60 templates in total). Each of these templates features colours which change dynamically based on the device state (open/closed, stopped/running, healthy / faulted etc with various alarm priority colours).
Current Setup
Currently the colours are defined using property bindings with transforms:
As I understand it this is among the recommended methods and should be computationally efficient when scaling to large systems.
However, from the point of general good programming practice this is far from ideal, for several reasons:
- Defining the colours and colour map on each of the objects means we have to program the same colours and maps 60 times. Defining the same functionality multiple times is typically considered to be poor programming practice. To quote my high school computing teacher, “If you have to write it twice, use a function”.
- By implication, it’s possible for one or more instances to be unintentionally different to the others (ie by mistake).
- Any change needs to be made 60 times which significantly increases the effort required to make changes or improvements.
Proposed Changes
What I would like to do is the following:
- Define the colours once using both styles, and memory tags of string type (containing the colour hex code). These would be be applied to the objects depending on whether a style or a colour binding is most appropriate in any given situation. The tags and styles would have names like “DeviceActive”, “DevicePassive”, “AlarmCritical”, “AlarmHigh” etc.
- Define some functions which map device states and alarm priorities to colours. The functions would have names like “getDeviceColorFromDeviceState”, “getColourFromAlarmPriority”, etc and would point to the styles and colour tags.
- Call those functions as needed on property bindings to dynamically assign the colours.
This would provide a way of defining and mapping the colours which has the following properties, which align with good programming practice:
- The colours themselves are centrally defined, once, and can be easily changed.
- The mapping functions are centrally defined, once, and can be easily changed.
I’ve trialed this method and it works well. I have a set of styles and colour tags I can reference, and scripts which get the relevant colours based on alarm priorities and device states.
I can call the scripts inside expression bindings and the scripts correctly obtain the colours.
Styles containing the colours:
Colour tags:
Centrally defined utility scrips which get the colours and styles based on alarm priority:
def getStyleFromAlarmPriority(priority):
style = ""
if priority == 0:
style = "Alarms/AlarmDiagnosticActiveUnAcked"
elif priority == 1:
style = "Alarms/AlarmLowActiveUnAcked"
elif priority == 2:
style = "Alarms/AlarmMediumActiveUnAcked"
elif priority == 3:
style = "Alarms/AlarmHighActiveUnAcked"
elif priority == 4:
style = "Alarms/AlarmCriticalActiveUnAcked"
else:
style = "Alarms/AlarmLowActiveUnAcked"
return style
def getColorFromAlarmPriority(priority):
color = ""
if priority == "Diagnostic":
color = system.tag.read("[default]_System/Colors/AlarmDiagnostic").value
elif priority == "Low":
color = system.tag.read("[default]_System/Colors/AlarmLow").value
elif priority == "Medium":
color = system.tag.read("[default]_System/Colors/AlarmMedium").value
elif priority == "High":
color = system.tag.read("[default]_System/Colors/AlarmHigh").value
elif priority == "Critical":
color = system.tag.read("[default]_System/Colors/AlarmCritical").value
else:
color = system.tag.read("[default]_System/Colors/AlarmNull").value
return color
def getPriorityStringFromPriorityInt(priorityInt):
priorityString = ""
if priorityInt == 0:
priorityString = "Diagnostic"
elif priorityInt == 1:
priorityString = "Low"
elif priorityInt == 2:
priorityString = "Medium"
elif priorityInt == 3:
priorityString = "High"
elif priorityInt == 4:
priorityString = "Critical"
else:
priorityString = "InvalidInt"
return priorityString
An example expression binding, running one of these scrips to obtain the colour:
runscript("alarms.getColorFromAlarmPriority",0,{view.custom.alarmpPriority})
I’ve tested this setup on a small number of devices and it works perfectly.
However:
According to the best practice guides I’ve read, large scale use of Python scrips is strongly discouraged by the developers for reasons of resource utilization due to Python’s lack of computational efficiency.
Ignition does not seem to provide for defining functions in Expression syntax, only in Python.
So my questions are:
- Is there any way to centrally define logical functions in Ignition which execute more efficiently than Python?
- If I replace all my colour maps with these Python scrips will it cause excessive compute utilization or cause other problems, both at current system scale or much larger scales?
- Are there ways of writing the Python scrips which might be significantly more efficient than using ‘if’ statements?
- Is there some other way of achieving what I’m trying to do that I might have missed? The intent is central definition of both the colours and the mapping functions.
- Has anyone deployed a large system where the colours and colour maps are centrally defined, or is everyone stuck with defining them N times using transform property bindings?
Appreciate any opinions, feedback or experience,
Angus




