Sorry in advance for the essay…
For the colours, I use an expression binding with a case statement. However it wouldn’t be a bad idea to create a python function to return the colour as well. Then you could make it a global script and call with runScript.
Update: I’ve just tested this and it seems to work really well. I have 350 objects copied and pasted that all use the same binding on the background colour, and there are no client performance issues whatsoever.
I would be interested in your opinions @pturmel and @PGriffith?
runScript function:
def getDeviceStatusColour(valueString, pulseValue, isTextBox=0):
colour = ""
textBoxSuffix = ""
# I use a slightly lighter grey if the colour is used beneath text, otherwise the text is drowned out
if isTextBox: textBoxSuffix = " Text"
if valueString == 'Stopped':
colour = system.tag.read('[Client]Styles/Colours/Devices/Stopped' + textBoxSuffix).value
if valueString == 'Stopping':
if pulseValue:
colour = system.tag.read('[Client]Styles/Colours/Devices/Stopped' + textBoxSuffix).value
else:
colour = system.tag.read('[Client]Styles/Colours/Devices/Stopped Flash').value
if valueString == 'Running':
colour = system.tag.read('[Client]Styles/Colours/Devices/Running').value
if valueString == 'Starting':
if pulseValue:
colour = system.tag.read('[Client]Styles/Colours/Devices/Running').value
else:
colour = system.tag.read('[Client]Styles/Colours/Devices/Running Flash').value
if valueString == 'Faulted':
if pulseValue:
colour = system.tag.read('[Client]Styles/Colours/Devices/Faulted').value
else:
colour = system.tag.read('[Client]Styles/Colours/Devices/Faulted Flash').value
if colour == "":
colour = system.tag.read('[Client]Styles/Colours/Devices/Invalid State').value
return colour
runScript expression binding on background colour:
runScript('shared.TEST.getDeviceStatusColour',0, {_Testing/Device Status}, {[Client]System/Pulses/500ms})
And my client tag colour definitions (might be relevant):

Remember to turn on combine repaints when you have lots of potentially flashing objects, to synchronise the repaints together.
Regarding the expression tags. I actually use string expression tags for my UDT Status and Mode tags to translate the values to their corresponding string description. This is so that I can use the same device template for many different versions of the UDT, even if they’re not using the same mode or status translations. You simply modify the translation in the UDT or at the instance, and the template will work. Ideally there should only ever be the one standard… but in reality, other unscrupulous contractors perform work and use their own standards who show little interest in using the standards already onsite
E.g.
case({[.]Status}
,0,'Stopped'
,1,'Running'
,2,'Faulted'
,'INVALID'
)
Initially I thought this might have a performance impact, and it probably does, however my status and mode tags don’t change every scan, they’re more likely to change once an hour, if that. So even with 1000’s of them having to update, the impact is relatively low.
For the pulses to control the flashing of colours, I use client tags and a client script running in a dedicated thread, fixed rate @ 125ms. I usually use the 500ms pulse, however I have a number of other values for just in case:
# Set this to the script execution time period in ms. This affects the minimum time of pulses that may be generated.
timeInterval = 125
# Time to count up to in ms. This affects the maximum time of pulses that may be generated
counterPeriod = 2000
tag = "[Client]System/Pulses/125ms Counter"
counter = system.tag.read(tag).value
# Increment the 125ms counter tag if the time is within the counterPeriod time, otherwise reset to 0
if counter > (counterPeriod/timeInterval - 1):
system.tag.write(tag, 0)
else:
system.tag.write(tag, system.tag.read(tag).value + 1)
# Write the pulse values to the client tags
system.tag.write("[Client]System/Pulses/125ms", int(counter*timeInterval / 125) % 2)
system.tag.write("[Client]System/Pulses/250ms", int(counter*timeInterval / 250) % 2)
system.tag.write("[Client]System/Pulses/500ms", int(counter*timeInterval / 500) % 2)
system.tag.write("[Client]System/Pulses/750ms", int(counter*timeInterval / 750) % 2)
system.tag.write("[Client]System/Pulses/1000ms", int(counter*timeInterval / 1000) % 2)