I need a global variable that is shared between a Gateway Startup script, a Gateway Timer script, and a project library script. It seems like the Gateway Startup and Gateway Timer script already share the same scope (they can both find the variable and execute fine), but the project library script throws a global name 'var' is not defined
error. How do I share the variable with the project library script? (the project the library is in is set as the gateway’s scripting project)
Gateway events sharing the same scope is an artifact of some backwards-compatibility decisions long ago. Generally, you would use a dictionary or similar defined in the top level of your project script. Those you can access by full name from elsewhere. (Elsewhere in the same scope, that is. “project library” isn’t a scope. Project libraries run in the scope they are called from.)
In that case, the script is being called from a Tag Change event script, which executes on the Gateway, no? So shouldn’t it have access to that global variable?
No, because only gateway events themselves have that funky “scope”–it is a python “scope”, not an Ignition “scope”. Everything else in Ignition follows modern python “scope”, where each script module’s top level is independent of other script module’s top levels.
You really should not use that legacy behavior. There be dragons. Use library script top-level named objects instead.
Ok, and how would I do that?
In a project script named myLibraryScript
:
mySharedVar = 45
def someFunction():
print mySharedVar
# the following is discarded as a function-local assignment
mySharedVar = 200
def anotherFunction():
global mySharedVar
mySharedVar = 300
In some other script:
myLibraryScript.mySharedVar = 100
myLibraryScript.someFunction()
myLibraryScript.anotherFunction()
myLibraryScript.someFunction()
Thanks Phil. You’re always so helpful. I didn’t even know this was possible and it’s exactly what I needed.
Be aware that the value will be lost if you edit and save the project--that causes a script restart and the project scripts will be re-run from scratch.
If you need a value that will survive that, you will need to use system.util.getGlobals()
. Take care with that, though: if you store any custom classes there, you will leak gobs of memory when you edit the origin project. Some more guidance:
Thanks for the heads up. I don’t think I’ll have issues with that with my setup. My library script creates the global var and sets the initial value. This “initialization” script is called from my gateway startup event (which is triggered on a project save) and then the global var is set from within that same startup script and read-only on other scripts.
I was looking into saving a dictionary in a tag but I get many errors.
This solution might also be a good approach.
mySharedVar
will be available forever?
A script that runs on a gateway update could solve that problem?
Yes and no. As long as there are no errors loading the script it is defined in, it will always exist. It may not have the expected value if the script restarts. There are gateway globals from system.util.getGlobals()
that can help, but there are memory-leakage pitfalls. Search the forum for "getGlobals" for numerous discussions on this.
Consider experimenting yourself until you understand all of the combinations.
I'm still struggling to grasp scoping. My desire is to have a script that returns a list or a dictionary, this script would run periodically from a gateway timer. It doesn't matter to me if the variable survives, as it will update as needed.
Getting the variable is where I'm lost. How can I store in a global variable and then retrieve from a project script?
Crude example:
globalList = []
def doSomething():
#From gateway event
globalList = ['one1','two2','three3']
def getSomething():
#From another script, project script
return globalList[1]
If I execute both from the script console it works. I played around with declaring globalList
as global but didn't make a different. Can someone please help me understand scoping related to my example?
In doSomething
, you are assigning a new object to globalList
, which causes it to become a local name in that function, not to replace the global. You have two approaches to address this:
-
use the
global
statement withindoSomething
to override the local treatment, or -
use list methods to modify
globalList
in place.
Modifying in place is most convenient when the top-level variable is a dictionary, not a list.
In addition, a module top-level variable in the gateway is not reachable in other projects, and is not reachable in a designer or vision client script environment.
Thanks Phil, running in the designer caught me up.