Where does a Perspective button script actually run?
Follow the packet. The click happens in the browser, but the browser does not run the script. The event travels over the session connection to the Ignition gateway. The gateway executes the Jython script in gateway scope, and the resulting writes go to the tag provider and from there to the device.
| Hop | What happens | Where failures show |
|---|---|---|
| Browser | Mouse event captured, sent to gateway session | Browser dev-tools console (system.perspective.print output) |
| Gateway session | Script executes in gateway scope | Gateway logs (script exceptions) |
Tag provider [Ignition_IO_01_NE_KRASNOPOL]
|
Write to NE_KRASNOPOL/LV_On
|
Quality code returned by system.tag.writeBlocking
|
| Device / driver | Value lands in the controller | Tag quality, driver diagnostics |
The failing line is system.util.invokeLater(...). That function exists only in Vision client scope. It is not defined in gateway or Perspective scope, so the call raises an attribute error the moment the script reaches it. The first writeBlocking has already executed and LV_On is True. The exception aborts the rest of the script, and nothing is left to schedule the False write. The tag stays latched.
Check 1: Did the "Reset tag" print ever appear?
Open the browser developer console for the session, or the Designer output console if you test in the Designer, and click the button once.
| Console output | Meaning | Next check |
|---|---|---|
| "Start script:", "Set path", "Set tag" only | Script died at invokeLater
|
Confirm the exception in the gateway logs, then go to Check 3 |
| No output at all | Event not bound or script not firing | Verify the event is on the button's mouse/click event and the view is saved and published |
| All four lines including "Reset tag" | Scheduling call did not throw; delayed write failed elsewhere | Check 2 |
In the gateway web interface, open the logs under Status > Diagnostics > Logs and filter on the Perspective or script logger. The stack trace names the missing invokeLater attribute. That confirms the branch.
Check 2: Does the write itself reach the tag with good quality?
Layer one first. Before debugging the timing, prove the path from gateway to device carries writes. system.tag.writeBlocking returns a list of quality codes, and the script above discards it. Capture and print it:
results = system.tag.writeBlocking([tagPath], [True])
system.perspective.print(str(results[0]))
| Returned quality | Meaning | Action |
|---|---|---|
| Good | Provider accepted the write | Timing is the only problem; go to Check 3 |
| Bad / not found | Tag path or provider name wrong | Copy the path from the Tag Browser with right-click > Copy Path |
| Bad / access denied or read-only | Security or tag configuration blocks writes | Check tag security and the tag's read-only setting |
| Bad / comm error | Driver or device connection down | Fix the device connection before touching scripts |
If LV_On goes True but never returns to False, the write path works. The defect is the timer.
Check 3: Which delay mechanism fits the gateway scope?
A Perspective event script should return fast. It runs on a gateway thread tied to the session. Anything that waits inside it either blocks that thread or depends on a thread that can die with the session, a project save, or a gateway restart.
| Mechanism | Available in Perspective scope | Survives page close | Survives project save / gateway restart | Verdict |
|---|---|---|---|---|
system.util.invokeLater |
No (Vision only) | n/a | n/a | Throws; root cause here |
time.sleep inline in the event |
Yes | Uncertain | No | Blocks the session thread; avoid |
system.util.invokeAsynchronous + sleep |
Yes | Usually | No; pending reset is lost | Works in testing, fails in the field |
| Deadline memory tag + gateway timer event | Yes | Yes | Yes; deadline stored in a tag | Reliable; recommended |
| Timer (TON/pulse) in the controller | n/a, HMI writes a trigger only | Yes | Yes, independent of Ignition | Preferred when the output drives equipment |
The reliable pattern stores the turn-off time as data, not as a pending thread. A memory tag holds the future deadline. A gateway timer event runs continuously, compares the current time to the deadline, and writes False when the deadline has passed. If the gateway restarts mid-pulse, the timer picks up the stored deadline on the next execution and still turns the output off.
If LV_On switches real equipment, put the 5 s timer in the controller and let the button write a start bit. The pulse then does not depend on the SCADA gateway staying up.
How do you build the deadline tag and timer event?
- Create a memory tag of type DateTime to hold the turn-off time. The example path below uses a placeholder,
[yourProvider]NE_KRASNOPOL/LV_On_OffTime; substitute your own provider and name. - Replace the button's mouse event script. Write the deadline first, then the output. That ordering prevents the timer from seeing
Truenext to a stale past deadline and dropping the output immediately.lvPath = "[Ignition_IO_01_NE_KRASNOPOL]NE_KRASNOPOL/LV_On" offTimePath = "[yourProvider]NE_KRASNOPOL/LV_On_OffTime" # placeholder deadline = system.date.addMillis(system.date.now(), 5000) q1 = system.tag.writeBlocking([offTimePath], [deadline]) q2 = system.tag.writeBlocking([lvPath], [True]) system.perspective.print("deadline=%s q=%s/%s" % (deadline, q1[0], q2[0])) - In the Designer, open the project's Gateway Events, add a Timer script, and set its delay. The delay is your precision: the output drops between 5 s and 5 s plus one timer period after the click.
- Paste the check logic into the timer script:
lvPath = "[Ignition_IO_01_NE_KRASNOPOL]NE_KRASNOPOL/LV_On" offTimePath = "[yourProvider]NE_KRASNOPOL/LV_On_OffTime" # placeholder lv, offT = system.tag.readBlocking([lvPath, offTimePath]) if lv.quality.isGood() and offT.quality.isGood(): if lv.value and offT.value is not None: if system.date.now() >= offT.value: system.tag.writeBlocking([lvPath], [False]) - Save and publish the project. Gateway event scripts run only while the project is enabled on the gateway.
Recurring pitfalls with this pattern:
-
Double execution: if the same timer script exists in more than one running project, each copy runs. That is harmless for an idempotent
Falsewrite, but it wastes cycles. Keep it in one project. -
Retrigger behavior: a second click inside the window pushes the deadline out another 5 s. If you need a fixed pulse that ignores re-clicks, have the button skip the write while
LV_Onis alreadyTrue. -
Manual override: an operator who sets
LV_OntoTruefrom elsewhere, while an old deadline is in the past, gets turned off on the next timer execution. Clear or refresh the deadline wherever else the tag is written. -
Clock skew: the deadline and the comparison both use gateway time through
system.date.now()in gateway scope, so browser clock drift does not matter. Do not compute the deadline client-side.
How do you verify the reset end to end?
- Open the Tag Browser with
LV_Onand the deadline tag visible, then click the button in a live session. - Confirm the browser console shows the deadline and two good write qualities.
- Watch
LV_OngoTrueand the deadline tag show a timestamp 5 s ahead of the gateway time. - Confirm
LV_Onreturns toFalsewithin 5 s plus one timer period. Check the gateway logs for timer-script exceptions if it does not. - Click the button, close the browser tab immediately, and confirm the tag still drops on schedule. This proves the reset does not depend on the session.
- Read the value at the device through the driver diagnostics or the controller's online view. Confirm the controller saw both the rising and the falling edge. A pulse visible only in Ignition has not been proven at the output.
FAQ
Why does system.util.invokeLater not work in Ignition Perspective?
invokeLater is a Vision-only function tied to the Vision client's UI thread. Perspective scripts execute in gateway scope, where that function does not exist, so the call throws and the script aborts.
Why does my Perspective button set the tag true but never reset it?
The first writeBlocking completes, then the next line raises an exception, so no reset is ever scheduled. Check the gateway logs for the stack trace, and see whether the prints after the failing line ever reach the browser console.
Why does the tag turn off immediately instead of after 5 seconds?
The timer event read True alongside an old deadline that was already in the past. Write the new deadline before writing the output, and refresh the deadline anywhere else the output tag gets set.
Why does a sleep in invokeAsynchronous lose the reset sometimes?
The pending reset lives only in a gateway thread. A project save or gateway restart kills that thread before it wakes up. Storing the deadline in a memory tag and checking it from a gateway timer event survives both, and the reset error is bounded by one timer period.