Moving Fault Codes

I have an interesting issue I've been struggling to find a good solution for. I have a device that reads engine fault codes. It has 40 registers for active codes, and 40 registers for active subcodes. If there's no active codes all the registers are '0', but if a code is active, the 1st registers will be filled with the appropriate code. These codes together can then be used to reference a table and find the error description. The interesting part comes when more than one code is active, as the new code pushes the other active codes down a register and takes up the first register.

It'd be pretty straightforward to have an alarm that just says "engine has active fault codes" or something, and then display a list of active codes and descriptions somewhere on the SCADA. Ideally though, I wanted to have 40 alarms that can be dynamically filled, so there's better tracking of what codes happened when in the alarm journal.

Also Ideally, I wanted to do all this in a UDT I could essentially copy/paste for every engine and just change a couple parameters in, but the scripting and binding in a UDT has been hard to work out for this situation.

Right now, my best solution for this is to have a global script run every 5 seconds or so, and set a dataset tag to the active codes. Then on that dataset tag I've got an on-value change script to do the actual alarm tag setting in a large scary script. This way was a bit more convoluted, but I'm trying to avoid this big script running when it doesn't need to.

Anyone have any interesting solutions to this that come to mind? I like to keep things elegant, and this is pretty far from that right now.

So the first register is always the most recent, like a FIFO?

For the most part yes. Though any code can leave the "queue" at any time potentially, so maybe not technically.

If the first register is always the most recent I would only look at that register and use tag history or SQL bridge to historize. Then you have a very simple mechanism to view history.

Does any of this have a timestamp coming in from the engine?

No timestamps, just 80 OPC tags. 40 Code registers, and 40 Subcode registers.

If multiple codes showed up at once I think you'd lose them with this unfortunately. I think you'd also lose some extra information like how long the code was active, though that's probably not super important as long as you have a history of the codes.

If you can have simultaneous codes, then read all 40. Regardless, IMO tag history or SQL in general is the way to go.

1 Like

Yea, I guess I could just write all the active codes to a database whenever there's a change. I wouldn't have things nicely included in the alarm journal, but it would be infinitely easier and still give me a history if needed. It's something to think about for sure, thanks for the idea!

Just as a followup, I ended up putting time into this to get what I ideally wanted. I made a UDT with parameters for the name and device, etc., and tags for alarms and codes. I accomplished the register tracking mostly with a value change script on a string tag that is bound to the values of the codes.

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):

	# Get a List of all active codes (not 000-0)
	activecodes = []
	for i in range(1,6):
		code = system.tag.readBlocking(["[.]Events/" + str(i) + "_Code"])[0].value
		subcode = system.tag.readBlocking(["[.]Events/" + str(i) + "_SubCode"])[0].value
		codestring = str(code) + "-" + str(subcode)
		if(code < 10):
			codestring = "0" + codestring
		if(code < 100):
			codestring = "0" + codestring
		if(code > 0):
			activecodes.append(codestring)

	# Get a Full list of the last codes on the alarms, set up a newcodes list with default "000-0" members,
	# and set up a list of the paths to the alarm tags for later writing.
	lastcodes = []
	newcodes = []
	alarmpaths = []
	for i in range(1,6):
		alarmpath = "[.]Event Alarms/" + str(i) + "_Alarm/Code.value"
		alarmpaths.append(alarmpath)
		lastcodes.append(system.tag.readBlocking(alarmpath)[0].value)
		newcodes.append("000-0")

	# Set the newcodes list to match the lastcodes list where the codes are still active, 
	# and set up a new trimmedactive list that has active codes not in the lastcodes list.
	trimmedactive = []
	for a in activecodes:
		trimmedactive.append(a)
		for i in range(0,5):
			if(a == lastcodes[i]):
				newcodes[i] = a
				trimmedactive.remove(a)
				break

	# Add the trimmedactive codes into the newcodes list at the earliest possible empty spot.
	for t in trimmedactive:
		for i in range(0,5):
			if newcodes[i] == "000-0":
				newcodes[i] = t
				break

	# Write the newcodes list to the alarms.
	system.tag.writeBlocking(alarmpaths, newcodes)	

I ended up just making a dataset tag with 1 column of strings that is code-subcode, and one column of code descriptions, though I may end up just moving this to a database. The alarms work perfectly now, though I had to do some work arounds to get the correct description on all the alarm events for the journal.

Pretty happy with this, the final code ended up being relatively simple, and now I can essentially copy/paste this into different projects for each engine.

1 Like