I have been thinking as to how I could accomplish this weird list in Ignition.
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)
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"]).value
linePath = tagPath.split('/Conveyor')
conveyorSlotsPath = linePath + '/Conveyor Slots'
conveyorSlots = system.tag.readBlocking([conveyorSlotsPath]).value
code = ""
if currentValue.value < 10:
code = "0" + str(currentValue.value)
code = str(currentValue.value)
if currentValue.value == 0:
for row in range(codes.rowCount):
if code == codes.getValueAt(row, "CODE"):
# Add to dataset tag
updatedConveyorSlots = system.dataset.addRow(conveyorSlots, [code, codes.getValueAt(row,"DESCRIPTION")])
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
ORDER BY timestamp_loaded DESC
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
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
- 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
return system.util.getGlobals().setdefault("suspect_conveyor", deque(maxlen=5))
#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 is None:
#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
#replace the item with placeholder
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
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.
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.
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)
old_queue = getConveyor()
if new_max >= old_queue.maxlen:
#left as an exercise for the reader, simplest is to just extend() and accept that data will disappear