Gateway tag change issue

I have a gateway script that is looking for the triggering tag to be true. Once the script fires it does some database stuff, logs a message to the log about what it did, and turns the bit off. The problem I am having is that it seems the script in firing mulitple times. Here is the script:

if newValue.getValue()==1:
	#Save the tag values to varibles
	Hanger = system.tag.read("SMS/SMS_HangerLoadedHangerNum").value
	
	#get the tag directly from the OPC server to guarantee the value is fresh
	server = 'Ignition OPC-UA Server'	
	tagPath = '[PLC]db_Hangers[' + str(Hanger)
	tags = [tagPath + x for x in ['].PartNumber', '].WorkOrder']]
	values = [x.value for x in system.opc.readValues(server, tags)]
	PartNumber = values[0]
	WorkOrder = values[1]

	#Get the recipe ID of the part number loaded into SMS_HangerDataBuffer so we know which coats to copy
	query = '''SELECT recipes_id FROM parts WHERE PartNumber = ?'''
	Recipe = system.db.runPrepQuery(query, [PartNumber], "foxall")
	RecipeID = Recipe.getValueAt(0,0)
			
	#Delete any existing data for the hanger
	query ='''DELETE FROM production
	WHERE HangerNumber=?'''
	system.db.runPrepUpdate(query, [Hanger], "foxall")	
	
	query ='''DELETE FROM hangers
	WHERE HangerNumber=?'''
	system.db.runPrepUpdate(query, [Hanger], "foxall")
		
	#Copy the part number's coats from the coats table and into the production table. Adding the hanger number that was scanned. 
	query = '''INSERT INTO production(HangerNumber, CoatNumber, RobotNumber, DryingRoom, MinDryTime, MaxDryTime, ManualPick) 
	SELECT ? AS HangerNumber, CoatNumber, RobotNumber, DryingRoom, MinDryTime, MaxDryTime, ManualPick 
	FROM coats WHERE recipes_id = ?'''
	system.db.runPrepUpdate(query, [Hanger,RecipeID], "foxall")
	
	#insert a new hanger into the hanger table
	query = '''INSERT INTO hangers (HangerNumber, recipes_id, PartNumber, CoatsCompleted, WorkOrder)
	VALUES (?,?,?,?,?)'''
	system.db.runPrepUpdate(query, [Hanger, RecipeID, PartNumber, 1, WorkOrder], "foxall")
	
	#Save to the system log what was saved
	Logger = "GatewayTagChange"	
	logMessage = "Added hanger number %s using part number %s" %(Hanger, PartNumber)
	shared.Global.StoreLog(Logger, logMessage)
		
	#Set the tag back to 0 to indicate we have finished
	system.tag.write("SMS/SMS_NewWaxLoaded",0)

Here is what I get in the log after turning the bit on:

Any ideas about what is going on would be appericative.

Perhaps it is getting executed three times while tag is true before the write makes it out of the write queue.

Update:

I moved the script from the Gateway event scripts to a Tag event script and commented out all the database writes. When setting the tag to true it still logged three times, so to check to see if the logger was the issue I added code to increment the HangerNum each time it is ran and the number increments three times and logs the number when the tag is set true. Any suggestions on what i am doing wrong?

	from time import sleep
	if currentValue.value:
		#Save the tag values to varibles
		Hanger = system.tag.read("SMS/SMS_HangerLoadedHangerNum").value
		
		#Save to the system log what was saved
		Logger = "GatewayTagChange"	
		#logMessage = "Added hanger number %s using part number %s" %(Hanger, PartNumber)
		logMessage = "Added hanger number %s" %(Hanger)
		shared.Global.StoreLog(Logger, logMessage)
		sleep(2)
		#Set the tag back to 0 to indicate we have finished
		system.tag.write("SMS/SMS_NewWaxLoaded",0)
		
		system.tag.write("SMS/SMS_HangerLoadedHangerNum",Hanger + 1)

This thread may be relevant:

FYI I just tested the below tag change script using memory tags and couldn't get it to mess up on count regardless of how fast/many times I clicked on the triggering tag value checkbox in designer:

	if currentValue.value and not previousValue.value:
		count = system.tag.read('_Test/var1').value
		tags = ['_Test/var1', tagPath]
		values = [count + 1, False]
		system.tag.writeAll(tags, values)

The not previousValue.value check ensures this is not triggered by a quality change (doubt that's the issue you're having, but it's possible without this check). You may also want to add and not initialChange.

I have tried similar solutions in the past but it didn’t work. I tried what you suggested with the same results. When I ran the tag change event today it is now logging that it ran eight times with each tag change. I find it interesting that it is logging that it is restarting the gateway scripts multiple times.

I had seen that post but was having trouble following the steps on how to remedy the issue but do have a feeling that might be the issue I am running into.

I might just have to call support tomorrow and see if they can help fix the issue.

This sounds like you are editing shared scripts for use with your tag events. Shared script edits cause gateway scripting restarts for every gateway project scope plus the gateway non-project scope. This is the number one reason to avoid tag events, as you cannot make them maintainable (with common code in the shared.* namespace) without disrupting state stored in any script module, shared or project. Gateway tag change events, as a part of a project, can use project scripts. Yes, they too are disrupted by project save, but don't disturb other projects.

As to your specific problem, there's not enough information to tell what's going on. Consider logging all of each event's properties so you can see what is changing. Share those results, and share all relevant code, including any other state. Also, you seem to be using an algorithm that writes back to a tag from within its own change event. That just begs for race conditions, with few exceptions. The most robust algorithms only permit writes to specific tags from one direction. Acknowledgements or other handshakes should use a separate tag.

1 Like

Ok, so you lost me.

What information do I need to be logging?

The tag that is triggering the event is a Boolean that is being latched in the PLC. When you are talking about race conditions I assume you are talking about where the tag is set to 0 at the end of the script. So you suggest I set a hand shaking tag to 1 and in the PLC set both the event tag and the hang shake tag back to 0?

To write each tag from only one direction:

  1. PLC sets trigger tag
  2. Script runs on trigger tag change, does stuff, and sets handshake tag
  3. PLC clears trigger tag when it sees handshake tag
  4. Script runs on trigger tag change and clears handshake tag
    This should make a race condition pretty unlikely,
2 Likes

A handshake tag like @witman describes is also a very reliable previous state indicator. Code would look like this:

logger = system.util.getLogger("myLogger")
logger.infof("Tag Change: ic=%s ev=%s nv=%s",  initialChange, event, newValue)
handshake = system.tag.read("[default]Handshake").value
system.tag.write("[default]Handshake", newValue.value)
if newValue.value and not handshake:
  # do stuff

As shown, if your script runs longer than your scan class, it cannot run again while the handshake is true, and will properly process a new trigger on initial change. A separate handshake that indicates success might be needed.

The PLC sets the trigger to “do stuff” and resets the trigger when the handshake turns on.

1 Like

Thank you both for your help. I applied your suggestion and it appears to have fixed the issue.

Now I just need to clean up the PLC side of things and test it more thoroughly.

Learn something new everyday.

OK, from my testing this seems to be working but one a new issue has arisen that i was hoping you would have some suggestions for. Because of they way my previous attempt worked if they was an error returned I was able to catch it because the bit would never bet set back to 0. Now I can’t do that. Do you have any suggestions on how I can check that the code inside the if statements completed? Here are my two tag change events that i am worried about:

#Loger to troubleshoot racing conditions
#logger = system.util.getLogger("myLogger")
#logger.infof("Tag Change: ic=%s ev=%s nv=%s",  initialChange, event, newValue)
handshake = system.tag.read("[default]SMS/SMS_HangerLoadedHandshake").value
system.tag.write("[default]SMS/SMS_HangerLoadedHandshake", newValue.value)
if newValue.value and not handshake:
	#Save the tag values to varibles
	hangerPath = "[default]SMS/SMS_HangerLoadedHangerNum"
	Hanger = system.tag.read(hangerPath).value
	
	#Get the tag directly from the OPC server to guarantee the value is fresh
	server = 'Ignition OPC-UA Server'
	tagRoot = '[PLC]db_Hangers[' + str(Hanger) + ']'
	tagPath = tagRoot + '.ShellCoatsCompleted'
	value = system.opc.readValue(server, tagPath)
	CompletedCoat = value.getValue()
	NextCoat = CompletedCoat + 1
		
	#Get the next coat for the hanger that was loaded
	query = '''SELECT RobotNumber, DryingRoom, MinDryTime, MaxDryTime, ManualPick
	FROM production 
	WHERE HangerNumber = ? AND CoatNumber = ?'''
	data = system.db.runPrepQuery(query, [Hanger, NextCoat], "foxall")
	
	#Put the tags into a list
	tagPath = tagRoot + '.CoatToProcess'
	tags = [tagRoot + x for x in ['.DryTimeMax', '.DryTimeMin', '.ManualStationRequired', '.RobotProgramNumber', '.ShellCoatsDryRoom']]
	
	try:
		#put the coat data into a list
		values = [data.getValueAt(0, "MaxDryTime")]
		values.append(data.getValueAt(0, "MinDryTime"))
		values.append(data.getValueAt(0, "ManualPick"))
		values.append(data.getValueAt(0, "RobotNumber"))
		values.append(data.getValueAt(0, "DryingRoom"))	
	except:
		#set values to 0
		values = [0,0,0,0,0]
		
	# Write all values to tags.
	#system.tag.writeAll(tags, values)
	system.opc.writeValues(server, tags, values)
	
	#Update the hanger table entry with the complete coat value
	query = '''UPDATE hangers
	SET CoatsCompleted = ?
	WHERE HangerNumber = ?'''
	system.db.runPrepUpdate(query, [CompletedCoat, Hanger], "foxall")
	
	#Clear the read hanger number
	system.tag.write(hangerPath, 0)
	
	#Save to the system log what was saved
	logger = system.util.getLogger("GatewayTagChange")
	logger.info("Copied coat %s data into hanger %s" %(NextCoat, Hanger))
	
#Loger to troubleshoot racing conditions
#logger = system.util.getLogger("myLogger")
#logger.infof("Tag Change: ic=%s ev=%s nv=%s",  initialChange, event, newValue)
handshake = system.tag.read("[default]SMS/SMS_NewWaxLoadedHandshake").value
system.tag.write("[default]SMS/SMS_NewWaxLoadedHandshake", newValue.value)
if newValue.value and not handshake:
	#Save the tag values to varibles
	hangerPath = "[default]SMS/SMS_HangerLoadedHangerNum"
	Hanger = system.tag.read(hangerPath).value
	
	#get the tag directly from the OPC server to guarantee the value is fresh
	server = 'Ignition OPC-UA Server'	
	tagPath = '[PLC]db_Hangers[' + str(Hanger)
	tags = [tagPath + x for x in ['].PartNumber', '].WorkOrder']]
	values = [x.value for x in system.opc.readValues(server, tags)]
	PartNumber = values[0]
	WorkOrder = values[1]

	#Get the recipe ID of the part number loaded into SMS_HangerDataBuffer 
	#so we know which coats to copy
	query = '''SELECT recipes_id FROM parts WHERE PartNumber = ?'''
	Recipe = system.db.runPrepQuery(query, [PartNumber], "foxall")
	RecipeID = Recipe.getValueAt(0,0)
			
	#Delete any existing data for the hanger
	query ='''DELETE FROM production
	WHERE HangerNumber=?'''
	system.db.runPrepUpdate(query, [Hanger], "foxall")	
	
	query ='''DELETE FROM hangers
	WHERE HangerNumber=?'''
	system.db.runPrepUpdate(query, [Hanger], "foxall")
		
	#Copy the part number's coats from the coats table and into the production table. Adding the hanger number that was scanned. 
	query = '''INSERT INTO production(HangerNumber, CoatNumber, RobotNumber, DryingRoom, MinDryTime, MaxDryTime, ManualPick) 
	SELECT ? AS HangerNumber, CoatNumber, RobotNumber, DryingRoom, MinDryTime, MaxDryTime, ManualPick 
	FROM coats WHERE recipes_id = ?'''
	system.db.runPrepUpdate(query, [Hanger,RecipeID], "foxall")
	
	#insert a new hanger into the hanger table
	query = '''INSERT INTO hangers (HangerNumber, recipes_id, PartNumber, CoatsCompleted, WorkOrder)
	VALUES (?,?,?,?,?)'''
	system.db.runPrepUpdate(query, [Hanger, RecipeID, PartNumber, 1, WorkOrder], "foxall")
	
	#Save to the system log what was saved
	logger = system.util.getLogger("GatewayTagChange")
	logger.info("Added hanger number %s using part number %s" %(Hanger, PartNumber))

	

Consider using a memory tag for “busy” in place of “handshake”, and then use two handshake tags to the PLC, “handshakeSuccess” and “handshakeFailure”. The “busy” tag just keeps your logic from stepping on its own toes. In your “do stuff” code, use try - except clauses and/or return values to catch errors. Set handshakeSuccess if you get all the way through, and handshakeFailure otherwise. Have the PLC reset the trigger if either turns on. Use an else clause at the top level to set both handshakes to false when the trigger is false.

1 Like