Persistent experiment

With respect to my previous post about data persistence, I conducted an experiment with a timer script on the gateway. Code in the script is as follows:

list = []
dict = {}

if system.tag.read("PLC/simulator/initPersistence").value:
	system.tag.write("PLC/simulator/initPersistence",0)
	list = [11,21]
	dict = {"val31": 31}	
else:
	system.tag.write("tempTag1",list[0])
	system.tag.write("tempTag2",dict["val31"])

This produces an error on the wrapper log (which no doubt the experienced would have expected):

By the way, the tag initPersistence is initialized to true in a startup script.
If I remove the list = and dict = {} at the top of the script the error message I get is:

So I conclude, as Kevin stated, the only way to persist the data between runs of the timer script is to use a tag, write to a file or some such. I've misunderstood Mr. Turmels response. :blush:

Saving the state of my FSM to an Ignition tag is sufficient to retain context between runs of the timer script. :smiley:
Code as follows works fine, even though I have to reload the finite state machine on each execution of the script.
Runtime is listed on the gateway as 1 ms.


# Tags is a convenience class to aid in reading and writing tags in a block 
#  with readAll and writeAll, and providing access as a python dictionary object
tags = project.TagIO.Classes.Tags() 
									
tags.read(project.TagIO.simulatorReadTags.tagList)

driveRequestedPositionFSM = project.fsm.FiniteStateMachine()

# Configuration of FSM is offloaded to a script file for clarity
project.FSMs.driveRequestedPosition.load(driveRequestedPositionFSM)

# the tag dictionary is provided to the FSM so that
# state transition functions can read and write tags as necessary
driveRequestedPositionFSM.tags = tags	

# Current FSM state is loaded from Ignition tag dictionary
driveRequestedPositionFSM.state = \
		tags.dict["[default]PLC/vessel/positionRequestedState"]

tags.dict["positionRequestedAchieved"] = 0
driveRequestedPositionFSM.currentStateExecute()
		
driveRequestedPositionFSM.scanTriggers(
	[
	tags.dict["[default]PLC/vessel/jogFWDpressed"]	# FSM should not reset
	, tags.dict["[default]PLC/vessel/jogREVpressed"] # FSM should not reset
	, tags.dict["[default]PLC/vessel/jogReleased"] # FSM should not reset
	, tags.dict["[default]PLC/vessel/positionRequestedSample"] # FSM resets
	, tags.dict["[default]PLC/vessel/positionRequestedCharge"] # FSM resets	
	, tags.dict["[default]PLC/vessel/positionRequestedBlow"] # FSM resets
	, tags.dict["[default]PLC/vessel/positionRequestedAdds"] # FSM resets
	, tags.dict["positionRequestedAchieved"]	# FSM does not reset
	, tags.dict["[default]PLC/vessel/driveStop"]	 # FSM resets
	]
	)

tags.writeTag(	# store new machine state
	 "[default]PLC/vessel/positionRequestedState"
	 , driveRequestedPositionFSM.state)

# write back changed tags to Ignition tag dictionary
tags.writeBack()
	 

You’ve misunderstood. Don’t try to initialize conditionally, or via external flag. Oh, and don’t use python built-in function names as your own variable names, like list and dict.[code]myList = [11,21]
myDict = {“val31”: 31}

def run():
system.tag.write(“tempTag1”,mList[0])
system.tag.write(“tempTag2”,myDict[“val31”])

def update():
myList[0] = system.tag.read(‘otherTag1’)
myDict[‘val31’] = system.tag.read(‘otherTag2’)[/code]

How about using system.util.getGlobals() from a tag event script?

I apologize to Phil for abusing the good nature of the Python programming language. I'm sure in good time it would have punished me for my sins.

Thank you Mr Park for giving a way forward and a likely explanation for my misunderstanding Phil's response. Documentation for system.util.globals mentions that the meaning of the word global changed very significantly with ignition 7.7.

In ignition 7.9, the gateway startup script:

global testvar
testvar = 11

and the timer script

global testvar
pyglobals = system.util.getGlobals()
print pyglobals
testvar = pyglobals["testvar"] + 1
print testvar

gives the following output

Extending to the original goal, and moving the initialization to the gateway startup script, the timer script for the finite state machine is modified to:

global driveRequestedPositionFSM, tags
pyglobals = system.util.getGlobals()

# get instance of the finite state machine
driveRequestedPositionFSM = pyglobals["driveRequestedPositionFSM"]

# Tags is a convenience class to aid in reading and writing tags in a block 
#  with readAll and writeAll, and providing access as a python dictionary object
tags = pyglobals["tags"]									
tags.reRead()	

tags.tDict["positionRequestedAchieved"] = 0
driveRequestedPositionFSM.currentStateExecute()
		
driveRequestedPositionFSM.scanTriggers(
	[
	tags.tDict["[default]PLC/vessel/jogFWDpressed"]	# FSM should not reset
	, tags.tDict["[default]PLC/vessel/jogREVpressed"] # FSM should not reset
	, tags.tDict["[default]PLC/vessel/jogReleased"] # FSM should not reset
	, tags.tDict["[default]PLC/vessel/positionRequestedSample"] # FSM resets
	, tags.tDict["[default]PLC/vessel/positionRequestedCharge"] # FSM resets	
	, tags.tDict["[default]PLC/vessel/positionRequestedBlow"] # FSM resets
	, tags.tDict["[default]PLC/vessel/positionRequestedAdds"] # FSM resets
	, tags.tDict["positionRequestedAchieved"]	# FSM does not reset
	, tags.tDict["[default]PLC/vessel/driveStop"]	 # FSM resets
	]
	)

tags.writeTag(	# store new machine state
	 "[default]PLC/vessel/positionRequestedState"
	 , driveRequestedPositionFSM.state)

# write back changed tags to Ignition tag dictionary
tags.writeBack() 

Code works as intended without the overhead of loading the FSM and recreating the tag dictionary on every cycle of the timer script.

The help, comments and suggestions are most appreciated

Jim

If I understand correctly, your FSM contains python class and callable objects. Placing those in the gateway’s globals will cause them to never be updated with new code as edits are made, and leaking the memory that contains those earlier versions of your scripts. You cannot put anything into the python globals that isn’t a native python datatype without huge problems. Fair warning for when your server falls over in production.

Just so I’m clear. What you are saying is: each time I change a gateway script and download it to the gateway, I leak some memory when I have an instance of my “FSM” and/or “Tag” object defined on the global scope.
That is good to know.

I thought I had tried this simple version of the code and that it hadn’t worked. I just tried it again, and it did work.
This is what you were saying all along – sorry for the confusion.
Startup script:

global var2 var2 = 2
Timer script:

global var2 var2 += 1 print var2
var2 increments on each cycle of the timer script just as you suggested I should be able to do.

[quote=“JimBranKelly”]leak some memory when I have an instance of my “FSM” and/or “Tag” object defined on the global scope.[/quote]No, I wasn’t clear. The globals dictionary you retrieve with system.util.getGlobals() is not safe for storing python objects whose type is defined in any user-supplied script. The ‘global’ keyword in python isn’t accessing this dictionary as of Ignition v7.7.
The legacy globals dictionary is a fine place to store pure state, as long as it is represented as native python or platform-supplied java data types. Serialization or pickling lets you store complex data in string form. But callables or user-supplied python class instances placed in the legacy globals will not get updated code when their origin script module is edited. Unpredictable operation can ensue.
I strongly recommend you establish your state transition matrix with its transition functions within your script module, presumably by assigning an instance of your FSM class to a top level name. Consider reading from a var in system.util.getGlobals() in the init() method of that class, and unconditionally writing to that var from that class any time state changes, essentially ‘backing up’ your object’s state to the legacy globals. Of course, you could do the same thing by serializing state to a string memory tag or any location that is independent of script module reloading.

Thanks for the advice Phil. I’ll need a little time to digest.

In the meantime, I followed Kevin’s suggestion and looked into the SFC module.
[attachment=0]vesselDrivePositionSFCb.jpeg[/attachment]
It gets the job done, but if you’ll forgive a polemic statement, ugh…
Please read the following with open mind and reservation of judgement – this is purely opinion.
My feeling is that flow charts are a good way to make simple logic look complicated.
My brain seems much better capable of dealing with complexity when it is in a grammatical form.
To illustrate: I find an arithmetic expression much easier to understand than the reverse polish notation equivalent.

That said the “action step” in the SFC module seems to model the concept of a “state” quite well.
Maybe a specifically designed finite state machine primitive would be a good addition.
When using the SFC to model an algorithm like this, it would be useful and a significant time saver to be able to see and edit all the underlying scripts and transition logic in one place – maybe some kind of XML representation.

After some digestion of Phils last comment, let me reflect my understanding of the issue:
In a situation where the gateway start-up script defines a global (lets call it myGlobal) that references a script (lets call it myScript). After the gateway is started it is possible to change myScript but when the change is published the startup script is not rerun. This leads to the potential that myScript and the myGlobal variable are not in sync. This could lead to problems in a timer script that uses myGlobal and references myScript. In the timer script the newer version of myScript will be used, whereas myGlobal will have a stored state based on the older version of myScript.

In my current situation, I’m using Ignition as a platform for a process simulator. I shut the machine and the gateway down every night. I feel the potential for a serious issue to be small. On the other hand, if your gateway is supporting an industrial operation, I can see that Phil has made a very important point. It could lead to an issue that is difficult to diagnose.

[quote=“JimBranKelly”] It could lead to an issue that is difficult to diagnose.[/quote]Been there. Pain is a great teacher. (-:

FWIW, you’ll find a discussion of some of this in the documentation for the objectScript() function from my Simulation Aids module. You might find its state dictionary useful in an expression tag. { Hint! }

Ok, some demonstration code. Assumes you have installed the Simulation Aids module.[code]# Finite state machine playground

Expected to be script module ‘shared.fsm’

scriptState = {‘counter’: 0}

Simple demonstration of script global state vs. expression global state

Create an Expression Tag of type ‘String’ and use this expression:

objectScript(‘shared.fsm.runState(state)’)

The counters will increment at the pace of the tag’s scan class.

def runState(expressionState):
scriptState[‘counter’] += 1
if ‘counter’ in expressionState:
expressionState[‘counter’] += 1
else:
expressionState[‘counter’] = 1
return “Expression Counter %d, Script Counter %d” % (expressionState[‘counter’], scriptState[‘counter’])[/code]After you first install this and watch the result string, the two counters will match. When you edit the shared script module, the script counter will be reset. When you edit the tag, the expression counter will be reset. Independently.

I’ll download and give it a try.

By the way I found an interesting comment from Nick Mudge in his blog, under the title of getting rid of Gstore.
http://nickmudge.info/?page=2
seems like perhaps this is the right way to do what I want to do. When my attention returns to this particular problem, I’ll try it out.

My observation is different. When I publish a change to a project, all of the projects on that gateway have their GW.shutdown() and GW.startup() ran.