Weirdly ordered list

I have been thinking as to how I could accomplish this weird list in Ignition.

image

On our production line we have a conveyor where parts marked suspect are placed. These parts needs to be dispositioned by an associate. When a part is placed on this conveyor a group of tags is updated with the suspected scrap code (I have a script on the code tag to get the description when it is updated)

image

Since there could be many of the same or different suspect parts placed on this conveyor the associate inspecting them needs to know which part is which. For this I want to display the parts in order they were placed on the conveyor. I accomplish this by when a code is updated I also add to the end a dataset tag

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	descriptionTag = tagPath.replace("Code", "Description")
	codes = system.tag.readBlocking(["[default]Database/MES/Codes"])[0].value
	
	linePath = tagPath.split('/Conveyor')[0]
	conveyorSlotsPath = linePath + '/Conveyor Slots'
	conveyorSlots = system.tag.readBlocking([conveyorSlotsPath])[0].value
	
	code = ""
	
	if currentValue.value < 10:
		code = "0" + str(currentValue.value)
	else:
		code = str(currentValue.value)
		
	if currentValue.value == 0:
		system.tag.writeBlocking([descriptionTag], [""])
		
	else:
		for row in range(codes.rowCount):
			if code == codes.getValueAt(row, "CODE"):
				system.tag.writeBlocking([descriptionTag], [codes.getValueAt(row,"DESCRIPTION")])
    # Add to dataset tag
				updatedConveyorSlots = system.dataset.addRow(conveyorSlots, [code, codes.getValueAt(row,"DESCRIPTION")])
				
				system.tag.writeBlocking([conveyorSlotsPath], [updatedConveyorSlots])

List Behavior

This list comes from an android app that this ignition app that I am building is replacing. The way this list is coded in the android app is:

  • List always contains placeholders for the available spaces(slots) on the conveyor (in this case 5)
  • new items are added the bottom of the list and other items are bumped up the list
  • Only the oldest item is actionable (can be dispositioned)

Does anyone have ideas on how I can accomplish this in ignition?

Is there any reason you wouldn't use a database table to keep track (with the added benefit of keeping history)?

  • id, integer, auto-increment, primary key
  • code, integer
  • description, string
  • timestamp_loaded, datetime
  • timestamp_unloaded, datetime

Now you can track the parts with a query such as,

SELECT TOP 5 
    id, code, description, timestamp_loaded, timestamp_unloaded
FROM tbl_suspect
ORDER BY timestamp_loaded DESC
LIMIT 5

Use the TOP 5 syntax for MS SQL and the LIMIT 5 syntax for MySQL and, I suspect, most others.

With the additional information you'll be able to do all sorts of calculations and charts.

In my mind I didn't care about keeping history but that might be a good idea. Though even with that I don't always want to show the last 5 records either only the ones that are currently on the conveyor which you can easily do by adding a where clause to that sql query but I still don't get the same list I had before.

The list I had before would also show (in this case) 5 placeholder items or slots and swap in the active ones starting from the bottom.

Heres an very basic animation showing what I am talking about. Orange is a suspect part on the conveyor and grey is a placeholder

Weird List10-11-2023_8-09-04

Looks like I will be compromising... again

It looks like a pretty standard queue, but I'm not sure what would be the cleanest (most Ignition-ic?) way to implement it here. I think you should be able to just use a collections.deque but that wouldn't survive a scripting restart by itself so is not a complete solution. To do that, you'd need to do one of the following:

  • Store the deque object in the system.util.getGlobals() dict (should be safe but might not be, maybe would need to be replaced with a Java Deque instead?)
  • Have the PLC provide a timestamp for each conveyor object so you can rebuild the queue on demand (at that point you probably wouldn't need to store the queue itself either)
  • Read and write the queue from a document or dataset tag (feels like a massive kludge)

Going to use the globals for now.

Set up a project script where you do the following:

from collections import deque

def getConveyor():
	return system.util.getGlobals().setdefault("suspect_conveyor", deque(maxlen=5))

def placeOnConveyor(item):
	#any items after 5 will cause the first item in line to drop out
	#to avoid, check len(conveyor) before appending
	#however, if the first item is a placeholder we'll kick it out no problem
	conveyor = getConveyor()
	if len(conveyor) < conveyor.maxlen or conveyor[0] is None:
		conveyor.append(item)
	else:
		raise IndexError

def getFromConveyor():
	#returns first real item and puts in a placeholder
	conveyor = getConveyor()
	item = None
	#if we've looped more than maxlen times, the whole conveyor is placeholders, therefore empty
	for i in range(conveyor.maxlen):
		item = conveyor.popleft() #raises IndexError if conveyor queue is empty
		if item:
			#replace the item with placeholder
			placeOnConveyor(None)
			return item
	else:
		raise IndexError

def conveyorAsList():
	conveyor = getConveyor()
	#we cheat here by filling in the slots that should be empty with placeholders
	return [None]*(conveyor.maxlen - len(conveyor)) + list(getConveyor())

You can have your tag change event call the above functions (checking for errors as needed) to fill the queue.
The last function gives you a normal python list that you can use for display purposes or convert to a dataset.

EDIT: Shoot, I made this without the placeholders first but adding them in kind of breaks the whole point of the queue. Give me a few minutes to fix that
EDIT 2: I think I have something functional. No warranties, implicit or otherwise

A quick test for your viewing pleasure:

EDIT 3: fixed place where the length of 5 was hardcoded. Now it's only set in the getConveyor() function.

1 Like

Thanks for this solution. I might look into implementing this later. For now I have deadlines to hit so I have to keep trucking along.

I'm pretty crusty right now since this is technically my third rewrite of this project due to changed or miss communicated requirements from higher up the chain. (Android/Websockets, Android/Ignition integration (1/2 done) , Full Perspective) so sorry if I come off as jerk.

1 Like

One thing I should mention is the conveyor may not always hold 5 parts it could be more or less. I am storing this value in a tag within my UDT structure

I tried to keep it pretty general, but on closer look I do see a place or two where the maximum size is hardcoded. You should be able to replace those with a reference to conveyor.maxlen, and just set maxlen to what is needed. If the max changes all the time during use, you can just remove maxlen entirely, which will let the queue grow to any size but will definitely break all of the logic that depends on a fixed-size queue.

1 Like

One thing I just thought of: because of how the deque and the globals dict work, if you want to change the maximum length after you've already started using the queue, you'll need to drop the old queue and create a new one (optionally copying over the old data).

def setConveyorMax(new_max, keep_data=True):
	new_queue = deque(maxlen=new_max)	
	if keep_data:
		old_queue = getConveyor()
		if new_max >= old_queue.maxlen:
			new_queue.extend(old_queue)
		else:
			#left as an exercise for the reader, simplest is to just extend() and accept that data will disappear
	system.util.getGlobals().update({"suspect_conveyor": new_queue})