Time tracking using Gateway Timer Script

Hello, I am new to ignition and python as a whole and have been trying to get the following setup on a small scale first before expanding it.

I have a VFD that will send Dint to my SCADA platform that designates VFD run time. I want the SCADA to do a periodic script ~ 1 min or so to update the memory tag stored on the gateway if the VFD time is greater than the memory tag, then vice versa so that if the VFD time is lower than the gateway it will be updated to the larger time. So far I have this and haven't been able to get any updated results with just looking at the VFD run state first.

# Function to update VFD run hours
def update_vfd_run_hours1():
    # Read current run state
	run_state1[0] = system.tag.readBlocking("[default]CUP/CNWR/PMP1/VFD/RunHours")
	memory_state1[0] = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")

    # Compare VFD and Gateway hours and set if VFD hours are greater
    	if run_state1[0].value > memory_state1[0].value:
			memory_state1[0].value = run_state1[0].value
			system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", 'memory_state1[0].value')

It checks out fine when I save it, but the function of the block doesn't actually do anything. I have also tried adding in a print memory_state1[0].value after the last line and it doesn't actually print anything.

Hopefully I am just missing something extremely obvious but I have read through the help articles many times.

Hi, yes from what I can read, there is a mistake

This isn't correct, you should instead use:
run_state1 = system.tag.readBlocking("[default]CUP/CNWR/PMP1/VFD/RunHours")[0].value

Something like this should work:

def update_vfd_run_hours1():
    # Read current run state
	run_state1 = system.tag.readBlocking("[default]CUP/CNWR/PMP1/VFD/RunHours")[0].value
	memory_state1 = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")[0].value

    # Compare VFD and Gateway hours and set if VFD hours are greater
    if run_state1 > memory_state1:
		system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", run_state1)

Please know that I highly advise against doing multiple system.tag.readBlocking, you should read both tags in one call.

Also, note that I only made it so that your script works. It might not have your expected behaviour. I'll let you handle potential logic flaws.

1 Like

Hmm, I took your advice and I moved around the [0] to the end (I am still unsure of what it actually designates to be honest) and I still cannot get the gateway memory tag to be overwritten with the value I am trying to read. I have even shortened it down within the script console to just the barebones of:

def update_vfd_run_hours1():
    # Read current run state
	run_state1 = system.tag.readBlocking("[default]CHW/VFD/New Instance/VFD1")[0].value
	#memory_state1 = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")[0].value

    # Compare VFD and Gateway hours and set if VFD hours are greater
    	system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", run_state1)
    	print run_state1

the print function doesn't show anything other than ">>>" in the script console interpreter when it should be showing my "run_state1" value.

The only other thing I can think of is that the incoming data is a double, and the memory is a float but I can change that.

Edit: I just tried the following code and I still cannot print any data

def update_vfd_run_hours1():
    # Read current run state
	run_state1 = system.tag.readBlocking("[default]CHW/VFD/New Instance/VFD1")[0].value
	print "result", run_state1.value

I am not sure what is going on here, all tags are readable and writable I just checked to be sure.

No, you should just do print(run_state1) to print the value.

I think this is not working because your indentation is not correct.

Also, if you are running this in the script console, make sure to actually call your function.

To further explain what's going on, the return value of a system.tag.readBlocking is a list of dicts QualifiedValues

(thanks @PGriffith and sorry ahah).
Each dict contains information about the tag, you have for instance .quality for the quality of the tag, and .value for the value of the tag, hence the [0].value .

See the last example in this manual page:

Not dictionaries, but QualifiedValue objects:

But the general point is correct. The [0] is Python's syntax for accessing the first element of a list or other sequence, and the .value is Python's syntax for accessing the value attribute of the given object.

1 Like

I ended up getting the code to work at the thought you mentioned my indentation might be wrong. I deleted the def function line and started at the top without any definitions and I am able to read and write tags without any issue. Here is what I put in the script console that worked:

run_state1 = system.tag.readBlocking("[default]CHW/VFD/New Instance/VFD1")[0].value
memory_state1 = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")[0].value
print "OG val", run_state1
memory_state1 = run_state1
system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", memory_state1)
print "memory state" ,memory_state1

It returns the strings as well as the data I need. I am now confident I don't understand the definition function now haha. I suppose I should look into further. For now I should mess with reading and writing multiple tags to save on memory resources.

If you or anyone wants to chime in on definitions I am more than happy to read it through. I will probably need to know about it in the coming weeks of my programming journey.

def is Python's keyword for defining a function.
You write def, then the function's name, then a list of possible parameters the function accepts, then a colon to open a new "scope". Every line of code you write below this colon that's indented to the same level is in the same "scope" as the function, and makes up the function.

An extremely simple function could be something like this:

def add(a, b):
	return a + b

This is defining the "add" operation, as a + b. But it's not actually doing anything yet - it's just saying "this is what the function will do, with the arguments I provide it".
If you then call the function (and print the result), you see something actually happen:

print add(1, 2)

I'd highly recommend a basic /intro Python course; most of this is just the vernacular of programming, and for better or for worse you will likely have to do some amount of scripting when working with Ignition.

3 Likes

Just when I was thinking I had it, I am set a step back. I have the same code in the script console, as well as the timer gateway script and the code does not function when I run the gateway timer but it does when I send it through the console.

run_state1 = float
memory_state1 = float

def updateVFD1(run_state1, memory_state1):
	# Read current run state
	run_state1 = system.tag.readBlocking("[default]CHW/VFD/New Instance/VFD1")[0].value
	memory_state1 = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")[0].value
	print "Original value:", run_state1
	if run_state1>memory_state1:
		memory_state1 = run_state1
		system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", memory_state1)
		print "updated memory state value:" ,memory_state1
	else: 
		print "not updated"

print updateVFD1(memory_state1, 0)

I get this error in the gateway log:

Traceback (most recent call last):
  File "<TimerScript:ShaesProject/CHP Run Hours @5,000ms >", line 16, in <module>
  File "<TimerScript:ShaesProject/CHP Run Hours @5,000ms >", line 6, in updateVFD1
NameError: global name 'system' is not defined

8.1.41 (b2024052809)
Azul Systems, Inc. 17.0.10

but when I remove / comment out the last "print" line it goes away, and still refuses to update the value when left in the gateway.

Gateway settings are fine: Enabled = true, Fixed Delay, Shared Threading, and a 5000 millisecond delay. Very strange :confused:

Have a look at this thread:

TL;DR: Add import system at the beginning of your updateVFD1 function

EDIT: I was mostly trying to fix your immediate issue but please you should definitely follow everything Paul said below.

The reason for that is complicated and hard to explain - basically, legacy code baggage. The easiest fix (and a good practice overall) is to move the function you're defining into the project library. That is, literally take the whole block of code you have starting from def and put it into a new project library script. Name it whatever you want, and then call it from your timer script using yourScriptLibraryName.updateVFD1().

That aside, you've got some syntax issues/misunderstandings that I'll correct here, but I'll again recommend a basic Python course.

run_state1 = float
memory_state1 = float

Python doesn't require you to initialize variables before you use them, with or without type information. You can just declare a variable with a value, and the type is implicit. Drop these lines completely.

def updateVFD1(run_state1, memory_state1):

You only need to write run_state1, memory_state1 (or anything else) at the top of your function if it's a parameter your function expects.
That is, look at these different function definitions:

def addTwo(a):
	return a + 2

def multiplyAndAddSomething(a, b):
	something = 13
	return (a * b) + something

def doSomething():
	print "I did something!"

You don't need to (and shouldn't) define a method as accepting parameters if it doesn't actually need them. In your case, you're overwriting any possible input parameters anyways, so it's completely pointless.

print statements, when code is executed on the gateway, go straight to the wrapper.log file, which can be annoying to get to. Prefer using system.util.getLogger when writing gateway scripts, so you can see the output directly in the gateway webpage.
More info here: Event Configuration Print Out to debug - #5 by PGriffith

There's no need to reassign the value of a local variable. This won't do anything to the tags - it's just having your CPU spin for a few cycles rewriting memory with no effect.

As mentioned above - there's no need to pass parameters in here - just invoke the function directly (updateVFD1()). Also, your function isn't returning anything, so there's no need to print the output - that's what the None value you're seeing in the script console is.

I would put the following into the project library, for now:

def updateVFD1():
	log = system.util.getLogger("VFD Debug")
	run_state1 = system.tag.readBlocking("[default]CHW/VFD/New Instance/VFD1")[0].value
	memory_state1 = system.tag.readBlocking("[default]CHW/VFD/Runtime/CHP-1")[0].value

	log.info("Original value: " + run_state1)
	if run_state1 > memory_state1:
		system.tag.writeBlocking("[default]CHW/VFD/Runtime/CHP-1", memory_state1)
		log.info("Updated memory state value: " + memory_state1)
	else: 
		log.info("Not updated")

And then in your timer script, just write:

whateverNameYouGaveYourProjectScriptLibrary.updateVFD1()

If you weren't aware of it, Ctrl + Space brings up the scripting autocomplete, and if you're on a recent enough version this will also bring up project library scripts.

2 Likes

Yes I think today should be dedicated to watching some python videos and doing some more practice before building any more scripts, I'm decent at graphics but the scripting and especially python is new to me (you can probably see where my ancient C+ arduino coding is messing with me haha).

I super appreciate all the help that I was given today and yesterday I did get the script to work as you said, I had to change some things around but it works just as intended and even updated it to work how I originally wanted it to. Thank you very much Samuel_Sueur and especially PGriffith for your time with helping me :slight_smile:

(I can't mention you since I am new but still deserve a shoutout!)

2 Likes