General question: I'm at the point where I loathe tag change event scripts, because if the script engine is restarted events get missed. I tend to use timed scripts instead, with a single read tag/check value if on it to gate the response. In theory, a tag change script should react faster and/or give less overhead than a script being called all the time. Is that noticeable?
Is there a good way to capture the "If tag goes high OR script engine starts and tag already High"?
I think for proper gating using a boolean tag it needs to be something like this:
if (newValue.getValue()) or (initialChange and (newValue.getValue())):
You should check the initialChange
flag before doing anything else. If critical, use that condition to check state that survives scripting restarts to determine the real previous value. Some candidates for such state:
-
A memory tag. Such would be written by the normal code whenever state changes (but not normally read). When
initialChange
is true, read it instead ofpreviousValue
. -
Tag history. If you are historizing the tag, read its last value from the historian before proceeding.
-
A python global dictionary, like
system.util.getGlobals()
or mysystem.util.persistent()
. (The latter doesn't have any legacy conflicts and works the same in all Ignition versions.)
So if using a boolean tag this should work:
value=False
if initialChange:
value=event.getCurrentValue()
if (newValue.getValue()) or value):
I want to 'catch' the case where the script starts with the tag already True.
That doesn't do what you think.
Can you give me an example that does work?
Thanks!
What do you want to happen when initialChange
is true? Ignore currentValue? (That would be typical in a stateless situation.)
Failure mode I encounter is that I miss the Tag going high, which leaves the system in a bad state. So my failure mode is my trigger tag stays high.
If my tag is low and I start the script, no issues.
my thought was that if initialchange was true, use the current value(1 in failure mode), then execute the script.
So, just check the current value. At worst, it'll double fire the logic, but that sounds OK for your case.
if currentValue.value:
... do something ...
Don't I still need initialValue check for the condition where the tag is high and scripts restart?
I'm sorry, getting confused here.
There are four possibilities:
-
Tag is low with initialChange true
-
Tag is high with initialChange true
-
Tag is low with initialChange false
-
Tag is high with initialChange false
When initialChange is true, you do not know the previous value (unless you use an alternate source of state, as mentioned above). Your logic can be arranged to underfire (not run at all in some cases) or overfire (run twice in the opposite cases).
You need to pick which stateless failure mode is more appropriate for your situation. If you cannot tolerate either of those two failure modes, you must record state elsewhere, to be recovered when initialChange is true.
IMO this is why using a counter is much better than using digital.
Yeah, no argument. The original coder liked putting a boolean on a transaction group result and using a tag change off that bool to trigger other tag writes. I'm just trying to get it reliable without redoing the whole thing.
Original state was if (newValue.getValue());
That can fail in the state where value is true.
I'm tempted to just make it a timer script instead, as being much simpler...
This comes back to the question I was hinting around;
What is overhead of a 1 second timer that reads a tag and exits, vs a tag change script?
The timer script seems much easier to handle in terms of if a do b, without any potential to miss events...
What's your obsession with missing events? Are you sure this is actually happening? It's pretty uncommon, and I'm not sure what you mean about script engine restarts being the cause.
P.S. a timer script is more likely to miss something. If your tag goes 0 -> 1 -> 0 you can easily imagine your timer script missing the 1.
Why do you say this? How does a 1-second timer know that the true value it is looking at is a rising edge versus a continuing value? You need prior state. A tag change script gives you that prior state except when restarting. A timer script has to maintain that state itself. Both events must deal with scripting restarts.
Oy! Ugly and unreliable.
Handshaking for reliability generally means replacing the transaction group in its entirety with proper scripted equivalent, with one-way writes in the right places in the handshake.
ok, fair question;. I'm not so much concerned with missing events as 'System left in state where it needs to be debugged.'. The appearance right now is that sometimes the tag change script does not fire; leaving the equipment in an unstable state(bad design of the data transactions!)
The event itself is a "slow" event, probably only happens on the order of every 30 seconds or so.
Basic design is that the log transaction fires, then tag is set high by the transaction and that runs the tag change script, which does a db query and then sleeps for 0.5 seconds before writing some tags. I suspect that the real problem is the sleep leaves it vulnerable to interruption.
After the sleep, tags get written and the handshake bit gets set to zero.
The design seems to be to write tags to confirm that values were stored in the database.
I think a more stable system would be to use a timed script that sees if the group Ok handshake is 1, then can execute with the sleep condition. If it gets interrupted before the handshake is written back to zero, it will just run again.
from time import sleep
logger = system.util.getLogger("myLogger")
if (newValue.getValue()):
barcode = system.tag.read("Damper/Station 20 Weld/LOG/LOG_CODE")
logger.info("Station 20:Verify Log Script Triggered, Barcode=" + barcode.value)
query = "SELECT [BARCODE],[WELD_OK],[WELD_NG],[WELD_PROGRAM] From [DAMPERS] where [BARCODE] = '" + barcode.value + "'"
result = system.db.runPrepQuery(query)
rows = result.getRowCount()
columns = result.getColumnCount()
sleep(.5)
if (rows != 0):
weldOK = result.getValueAt(0,"WELD_OK")
weldNG = result.getValueAt(0,"WELD_NG")
weldPR = result.getValueAt(0,"WELD_PROGRAM")
if (weldOK is None) or (weldNG is None) or (weldPR is None):
system.tag.write("Damper/Station 20 Weld/LOG/LOG_NG",1)
elif ((weldOK or weldNG) & (weldPR > 0)):
system.tag.write("Damper/Station 20 Weld/LOG/LOG_OK",1)
else:
system.tag.write("Damper/Station 20 Weld/LOG/LOG_NG",1)
else:
#logger.info("Barcode Not Found")
system.tag.write("Damper/Station 20 Weld/LOG/LOG_NG",1)
system.tag.write("Damper/Station 20 Weld/LOG/LOG_CHECK",0)
system.tag.write("Damper/Station 20 Weld/LOG/WELD_COMPLETE",0)
The LOG_CHECK tag is the tag that triggers the script.
If I instead do a simple
enable=system.tag.read("log_check")
if enable:
blah blah blah
system.tag.write(log_check,0)
Then I think I can never get "stuck" in the condition where the tag is 1.
Could also do a age of timestamp instead of the sleep; but not sure that adds any value;
I am leery of adding overhead, though, which is the only reason I don't immediately do a timed script.
This .sleep()
does nothing for the booleans you are retrieving, as they are already static in the result dataset.
{ Tip: If you are using any form of .sleep()
or busy-looping, in a system like Ignition, you've already screwed up. }
Yeah, completely agree about the sleep being completely useless. It might have made sense before the query, but even then this event is triggered by a log to the db, which the query is checking.
It seems to me that everything that needs to happen in sequence should be in a single script, relying on only the natural delays of query execution and tag operations.