While loop script is not working

Hello, I made a script function counter.

it is supposed to check for new instances in which the bool tag turns true. And work as a counter while hourly intervals are active (e.g. when it is 15:00 hrs return the number of instances for which the bool tag was true). Maybe I am doing something wrong? I am new to ignition and python to be honest...

I was told that I cannot use the database on site for reasons unknown to me, so I have to find the way to achieve this.

image
My goal is to write the number of changes of the boolean tag using my counter function with the respective time argument for each of the labels (in the above image) to be binded.

Any help would be greatly appreciated

I would make a tag that has the expression dateExtract(now(10000), 'hour') == 15, and make a tag change event so when that tag is true, then you execute your logic to check for new instances of bool tags.

2 Likes

To me it looks like you set up an infinite loop. After the while starts your not updating now or time. Time is passed in to start the function and now is only updated before your loop starts. With that, unless there is something I’m missing, your loop will never change the condition required to end the loop. On top of that, your changeVal is also looked at before you go into the while loop. I think @bkarabinchak.psi has the right idea, but me personally I would do the tag change event script on your bool tag that your wanting to count. Then set up memory tags for the different hours that you want to look at. In your script you can look at the current hour when your bool is true and increment your memory tag value for that hour. Also as a side note, your changeVal is returning a list of qualified values. You need to extract the value from it.

Here is a quick example of the idea I’m thinking. Its assuming you create tags that are in the format of HourlyCount# where # is the hour.

	if not initialChange:
		if currentValue.value:
			hour = system.date.getHour24(system.date.now())
			tp = '[Sample_Tags]HourCount%d' % (hour)
			tv = system.tag.readBlocking([tp])[0].value
			tv += 1
			system.tag.writeBlocking([tp],[tv])
3 Likes

Just reminded of one other option but if you are using 8.1.6 or higher you could also use a Scheduled Script to run at a predetermined time

@bkarabinchak.psi where he wants to use it as a counter I don't think he wants to do a scheduled script. To do that he would have to leave a loop running non-stop the entire hour. If he was just checking the value at set intervals I could see that being perfect but I think he wants a continuous count.

1 Like

I am not quite sure I understand what you’re trying to do, but let’s address a few things about your script:

def counter(time):
	date = system.date.now()

	agg = 0
	changeVal = system.tag.readBlocking(["[Sample_Tags]Bool_array[0]"])
	now = system.date.getHour24(date)

	while now == time:
		if changeVal == True:
			agg += 1

	print agg

count(15)

Here are the 2 possible outcomes (assuming the datetime logic is sound):

  1. it’s 15:** : now == time returns true, the loop is entered. Since this condition stays true, it’s an infinite loop.
  2. it’s not 15:**: now == time returns false, the loop is not entered, agg's inital value (0) is printed.

I think you had something like this in mind:

def counter(time):
	change_val = system.tag.readBlocking(["[Sample_Tags]Bool_array[0]"])
	agg = 0
	while system.date.getHour24(system.date.now()) == time:
		if change_val:
			agg += 1
	print agg

counter(15)

Here, the date is evaluated at each iteration of the loop, which gives it a chance to exit.
But, it is going to loop continuously until it’s 16:00. Is that what you actually want ?
Even if that’s the case, you may want to set up some delay between each iteration, with sleep() for example.
Note that you shouldn’t test for truth with == True in python.
Also, if you’re expecting to get different values for change_val, there’s another mistake, as it won’t be reevaluated at each iteration. You’d need to put that tag read in the loop. You also need to “unpack” the results of the readBlocking call, as it return a list of qualified values: readBlocking([path])[0].getValue()

def counter(time):
	agg = 0
	while system.date.getHour24(system.date.now()) == time:
		if system.tag.readBlocking(["[Sample_Tags]Bool_array[0]"][0].getValue()):
			agg += 1
	print agg

counter(15)

That’s an astronomical number of tag reads !

Now, this will only execute when the function is called. If you wanted to make sure it executes from 15:00 to 15:59:59, you’d need to actually run that loop continuously, all day long.

There are a lot of ways to do things in ignition, some better than others. I don’t think this would be the best way of doing what you want to do, even without knowing what it is you want. Not even a good way, most likely one of the worst.

If you could be a little bit more explicit about your issue, I’m sure folks around here will help put you on the right track.

1 Like

An example:
Set up a document tag, and initialize it with the following script:

initJson = system.util.jsonEncode({'hourly': [{'hour': 0, 'value': 0}]})
system.tag.writeBlocking(['[Test]Counter'], [initJson])


Create a Gateway Tag Change script, adding the trigger tag to the paths. Yes, I realize the picture says Client Events. That’s because it’s what I use for testing. Do not use it there in production.

The script:

# Prevent trigger and startup
if not initialChange:
	# Get the value, qulaity and timestamp of the changed tag
	trigger, quality, timestamp = newValue.value, newValue.quality, system.date.format(newValue.timestamp, 'MM-dd HH:00')
	
	tags = system.tag.readBlocking(['[Test]testTag','[Test]Counter'])
	
	if trigger == True and quality == 'Good':
		countersDict = system.tag.readBlocking(['[Test]Counter'])[0].value.toDict()
		# Get the hourly Array
		hourlyArray = countersDict['hourly']
		# Get last counter in the array
		current = hourlyArray[-1]
		# If the event happens in the same hour, add 1.
		if current['hour'] == timestamp:
			current['value'] += 1
		# Otherwise, append a new counter
		else:
			hourlyArray.append({'hour':timestamp, 'value':1})
			# If we go over the limit of counters, remove the oldest one.
			if len(hourlyArray) > 36:
				hourlyArray.pop(0)
		# Re-encode to json
		json = system.util.jsonEncode(countersDict)
		
		system.tag.writeBlocking(['[Test]Counter'], [json])


This will give you a rolling set of counters for the last n event hours. This example is set at 36.

That said, this is only a rough script. It will not, for example, put in a zero counter if there are no events for it.

1 Like

Thank you everyone for the expeditious help! I realize it is not a good method... but I figured (before, in my head) it was the easiest to implement. There are 5 lines that make up one production machine. Each one of the five lines has about 17 tags (with machine signals) attached to them.

My main goal is to show the aggregate error outputs for the 5 lines in 1 hour (continuous) intervals. I have as shown in the second image of the post, 24 labels for "scrap" in this case. I didn't want to create 24 tags x 2 (scrap and good parts) + (possibly other tags ). I thought I could just use the labels directly as temporary "memory cells" for the counters

Maybe on my previous post, I explain a bi more in detail what I wanted to do:

@pascal.fragnoud Thanks for taking the time, this was helpful for my general understanding!
But when I try to run your function I get the error:

looking at the system.tag.readBlocking doc I can't find much.

Right, my bad, that’s a misplaced parenthesis…

readBlocking takes a list of paths, and returns a list of qualified values.
Since you’re passing only one path, you get a list of exactly one item, which you get with [0]. The item you get with this is a qualified value, which has a method getValue().
So, step by step:
call the function with a list containing one path: tags_list = system.tag.readBlocking([path])
get the first item in the returned list: tag = tags_list[0]
get the value: tag.getValue()
or, in just one line : system.tag.readBlocking([path])[0].getValue()

In the script I posted before and which you copied, the whole thing is in the readBlocking call:
system.tag.readBlocking([path][0].getValue())
There, I was actually extracting the first path in the paths list, then calling getValue() on it. The first item in that list being "[Sample_Tags]Bool_array[0]", which is a string. This is why you got the error "str object has no attribute getValue"

@pascal.fragnoud I realized after a little while. But I am sort of in the same situation. Maybe I should have read more on while loops; the agg value is only returned when the loop breaks (i.e. when the time condition is not met) and apparently is iterating thousands of times. I got a return of 821392 for the last 5 minutes of the hour, which of course was not the real case at all.
Just to get the hang of this, I started using an integer memory tag with an event script

But it's not working...
I will start looking deeper into Jordan's suggestion and see what else I can do

Mariano, when you post a screenshot we cannot copy and edit the code in our answers. Paste the code as well and use the </> button to format it.

1 Like

Do you mean like this?

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

agg= currentValue - previousValue
if system.date.getHour24(system.date.now()) == 14: #and currentValue.value != previousValue.value: #and initialChange == FALSE:system.tag.readBlocking(["[Sample_Tags]Bool_array"])[0].getValue()
	agg += 1
system.tag.writeBlocking(["[~]return output.value"],[agg]) 

Yes, I don't think you should be using this loop system here. I was merely trying to explain a few things about loops and why it wasn't behaving the way you expected it to.

and apparently is iterating thousands of times. I got a return of 821392 for the last 5 minutes of the hour, which of course was not the real case at all

Indeed, there's not much in your loop, and it's quite fast. As I said before, it's going to run continuously until the condition is not met anymore, which results in A LOT of iterations, even over a short period of time.
Then, there's another fallacy here: I don't think you actually want to increment your agg variable for each iteration. This does not result in the number of times something happened, only in the number of iterations where some condition was true.

As for code formatting, I suggest you enclose your code in three backticks ```, and write the language name after the first series:
image
This enables syntax highlighting. Pretty.

1 Like

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	
	
	agg= currentValue - previousValue
	if system.date.getHour24(system.date.now()) == 14: and currentValue.value != previousValue.value: #and initialChange == FALSE:system.tag.readBlocking(["[Sample_Tags]Bool_array"])[0].getValue()
		agg += 1
	system.tag.writeBlocking(["[~]return output.value"],[agg]) 

Don't put the : between two conditions, only at the end:

if condition1 and condition2 or condition3:
    dosomething()
1 Like

Thanks, with all the pasting I overlooked it! For now I am implementing an array with the logic I need directly in the PLC and writing into an ignition OPC (and tag). I will look for other, maybe more useful alternatives as well in the meantime ... but it's great and I appreciate to get this much help from the community!