Top 3 Downtime Script

Hi everyone,

I am trying to make a dynamic table that will update on a set time. It will show the current downtime time, current downtime occurrences, and previous downtime occurrences of the previous shift. It will sort it all by occurrences. All this info comes from the cells PLC. I am having a problem I can’t seem to get around I keep getting an error on the sorting of the below code. The error is Row 0 doesn’t have the same number of columns as header list. Below the code is a copy of the first few lines from the exception at the top you can see what the print data returns. Please help.

Edit: FYI, I am really new to this scripting. This is the first real scripting I have done in Ignition.

def DT(line):

	from operator import itemgetter
	
	pathList = []
	data = []
	
	pathList = ('[Mexico]Die Cast/'+line+'/Cell Misc/Fault Occurrences/Fault_Occur/fCell/Name','[Mexico]Die Cast/'+line+'/Cell Misc/Fault Occurrences/Fault_Occur/fCell/Current_Count','[Mexico]Die Cast/'+line+'/Cell Misc/Fault Occurrences/Fault_Occur/fCell/Current_Time','[Mexico]Die Cast/'+line+'/Cell Misc/Fault Occurrences/Fault_Occur/fCell/Prev_Count')
	
	headers = ['Equip', 'Current_Count', 'Current_Time', 'Prev Shift Count']
	 		
	data = [[tagName.split('/')[-1], qualified.value] for tagName, qualified in zip(pathList, system.tag.readAll(pathList))]
	
 	print data
	dataset = system.dataset.toDataSet(headers, sorted(data, key=itemgetter(1), reverse=True))
[[u'Name', u'Cell'], [u'Current_Count', 2], [u'Current_Time', 68], [u'Prev_Count', 0]]
05:48:11.364 [AWT-EventQueue-0] ERROR com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter - <HTML>Error executing script for event:&nbsp;<code><b>actionPerformed</b></code><BR>on component:&nbsp;<code><b>Button</b></code>.
com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last):
  File "<event:actionPerformed>", line 1, in <module>
  File "<module:project.Top3DT>", line 15, in DT
IndexError: Row 0 doesn't have the same number of columns as header list.

Print out your data variable and see if it is a list or list of lists. If it is only a list then it will treat each item as a list of characters which will not match your header count

["item1","item2"]  //List
[["item1"],["item2"]]  //list of lists require for dataset

This is only two elements. You are providing headers for four elements per row. Add more values per row, or delete some headers.

2 Likes

So as @pturmel has pointed out your data list only has 2 column’s of data per row. I think that you have misunderstood what format the system.dataset.toDataSet function is expecting the data to be in.

The headers list needs a string for each column header, and the data list needs a List of Lists, where each list in the list represents a row of data.

So your data should look like this when printed

[['Cell',2,68,0]]

So your function can be simplified to the following:

def DT(line):
    pathRoot = '[Mexico]Die Cast/%s/Cell Misc/Fault Occurrences/Fault_Occur/fCell/' % line
    tagNames = ['Name','Current_Count','Current_Time','Prev_Count']
    data = [[q.value for q in system.tag.readAll([pathRoot + t for t in tagNames])]]
    headers = ['Equip', 'Current_Count', 'Current_Time', 'Prev Shift Count']
    return system.dataset.toDataSet(headers,data)

Note, that if you are using 8.0 up you should be using readBlocking in place of readAll

Thanks everyone for the replies! I knew it was just something to do with my lack of knowledge into Jython.

@lrose I do really like that code, much easier to add more paths. I have 15 folders (paths) with 4 tags each (Name, Current_Count, Current_Time, Prev_Count). So up to 60 tags and that is only 1 cell. I have 19 cells to do in total. The only 2 things that change in the tag path is where you have the %s, which would be the cell name, and then fCell would become fDCM, fTP, etc…

Anyways, I tried out your code and it gives me a error on toDataSet. The error is java.lang.ClassCastException: org.python.core.PyInteger cannot be cast to org.python.core.PySequence.

Oops, that’s what I get for not testing it. :slight_smile:

You need an extra set of brackets around the list comprehension for the data declaration.

data = [[q.value for q in system.tag.readAll([pathRoot + t for t in tagNames])]]

I’ve corrected the mistake in the code above.

No problem. Way better than what I could come up with testing :laughing: . I have been able to get it to iterate through another folder so now it goes through and gets the values for all 4 tags in fCell and fDCM but I need fDCM to start a new row in the table. What would be the best way to do this?

Well, the easiest way would be to add a for loop, like so:

def DT(line):
    data = []
    tagNames = ['Name','Current_Count','Current_Time','Prev_Count']
    headers = ['Equip','Current_Count','Current_Time','Prev Shift Count']
    for folder in ('fCell','fDCM'):
        pathRoot = '[Mexico]Die Case/%s/Cell Misc/Fault Occurrences/Fault_Occur/%s/' % (line,folder)
        data.append([q.value for q in system.tag.readAll([pathRoot + t for t in tagNames])])
    return system.dataset.toDataSet(headers,data)

For the not as simple, but ultimately better IMHO, way I tend to recommend that you do all of your tag reads at the start as opposed to each time through the loop. This will really speed up your execution time. I have a helper function that I use in a shared script that takes a list of paths and returns a dictionary.

def getTagValueDict(paths,root=''):
    values = [v.value for v in system.tag.readAll(paths)]
    names = [p[len(root):] for p in paths]
    return dict(zip(names,values))

This returns a dictionary where the keys are tag names and the values are the tag values. In your case

{'fCell/Name':'Cell','fCell/Current_Count':2 ....}

Then your function would look like this:

def DT(line):
    root = '[Mexico]Die Case/%s/Cell Misc/Fault Occurrences/Fault_Occur/' % line
    folders = ('fCell','fDCM')
    tagNames = ['Name','Current_Count','Current_Time','Prev_Count']
    tagValues = getTagValueDict([root + f + '/' + n for f in folders for n in tagNames],root)
    data = [[tagValues[f + '/' + n] for n in tagNames] for f in folders]
    return system.dataset.toDataSet(headers,data)

Note that depending on the version of Ignition you are on, where you store the getTagValueDict() function will change, and so the way you would call that function will also change. Of course you don't have to put that code in a seperate function at all. I have found that I am constantly wanting to call a number of tags at once in a script and honestly,

values['fCell/Name']

is much more readable than

values[0]

It is also possible to move the specification of .value from the getTagValueDict and return the qualifiedValue objects for the tags, then this same function can be used if you're interested in the timestamp or quality instead of the value.

I am on V7.9. I tried the second option because I could see me needing to read multiple tags at once again during all this. I put the getTagValueDict() as a shared function. It did run and call the shared function put errors for global v not assigned.

On a side note I truly appreciate your help with this and one day hopefully I can learn enough about this to be able to write these kinds of scripts. Time to go sign up for some Python coding classes…

Be very careful with dictionaries. The native dict() object in python does not maintain the order of elements–you get them back in hash order for iterators, which is rather pseudo-random.

2 Likes

That is again my fault, there is an error in that code snipit. Wrote it from memeory :man_facepalming:

def getTagValueDict(paths,root=''):
    values = [v.value for v in system.tag.readAll(paths)]
    names = [p[len(root):] for p in paths]
    return dict(zip(names,values))
1 Like

Thank you for the words of wisdom. I love learning all these new things.

Again no problem. You got a good memory if you can do that from just memory or been doing this for a while either way I am impressed. I will try it again tomorrow when I get back in to work.

Thank you very much for the help. That worked very slick. I only had 1 issue. It was not returning any data but was running. Found a typo in the root path. Case should be Cast changed it, added the rest of the folders and a sort on the dataset. Everything works as I need it to now. This has opened my eyes and I now realize I am far from where I want to be on scripting. Again thank you.

1 Like

This has opened my eyes and I now realize I am far from where I want to be on scripting

Some of this may be over your head now (and that's okay!) but I recommend this video a lot for Python devs of all levels, Raymond Hettinger's Transforming Code into Beautiful, Idiomatic Python

2 Likes

Thanks I will definitely check it out!

1 Like