Best way to trigger a script after a time delay without pausing a script

I want to call a script to run based on a boolean tag changing state. When the bit goes low I want to wait 30 seconds and then do some simple math by reading in a value and then writing to a tag. Right now I have a tag event value change script that works great except it does not have the delay in it. What would be the best way to add a delay without essentially pausing the script? Would it be when the bit goes low trigger a secondary tag. That tag would have a script on it to run a timer and after 30 seconds trigger another tag that would actually do the math? Although this kind of seems like the same thing. Suggestions?

import threading

def delayed_function():
    print("This function runs after a 5-second delay.")

# Set the timer
timer = threading.Timer(5.0, delayed_function)
timer.start()
1 Like

I made a UDT that behaves similar to Rockwell's TON/TOF functions. You may find it useful. Or maybe not. :wink:

TimerUDT.json (1.4 KB)

3 Likes

I attempted to do that within a gateway tag change script and the delay portion is not working. It does not seem to be executing the function I have inside the script. Can I not have a function within the overall script? Am I not allowed to have a function within a gateway script like this?

Nice! I have tried the threading.Timer function and it does not execute the delay. It writes the value right away. I think I will use your UDT. When I am ready to start timer turn on the monitor bit. Then I can apply a script on the TON_Flag to set the value to 1!

I tried this and for whatever reason it did not work, although it seems it should! Thanks for pointing this out though!

Using a delay in a script is fraught with peril in Ignition. Jordan's UDT works because it uses the same technique a PLC would: it repeated executes expressions to "see" what it should do/report.

If you cannot use an expression tag that regularly executes, the safe scripting alternative is a repeating (gateway) timer event that reads relevant tags, evaluates the timing logic, and runs the next step when appropriate. No pause/sleep/delay used in the script.

If you absolutely have to schedule something in the future, use the gateway context's scheduler. (But beware: not supported by IA.)

Thanks Phil. Yes I remember I did that a few years ago when I first was programming with Ignition and it freezed the gateway so lesson learned. Not sure why threading.Timer did not work but the timer UDT worked perfect. Thanks for the advice

Because it is a sleep under the hood.

Anyways, using jython stdlib stuff is prone to problems, unless it is very generic. Importing threading is almost always a mistake in Ignition.

There is another solution you may try:

import asyncio

async def delayed_function():
    await asyncio.sleep(5)  # Delays for 5 seconds
    print("This function runs after a 5-second delay.")

async def main():
    # Schedule delayed_function to run after a delay
    await delayed_function()

# Run the async main function
asyncio.run(main())

Really, stop with the ChatGPT ideas. Jython doesn't have async and await keywords.

14 Likes

Hi. I have been avoiding using any pause/sleep/delay by creating delayed alarms and then dealing with the desired reactions in the Tag Event Scripts.

On my systems, I capture almost all tag transitions as an alarm with the priority "Diagnostic". When I want a delayed reaction, I delay the alarm associated with that change (or create a new one) and then write the action in the Alarm Active script.

2 Likes

Jordan, I've been exploring the UDT you shared but I'm running into a limitation where I can't seem to get the Monitor tag alarms to trigger any faster than 500ms (adjusting tag group rate doesn't seem to affect it).

Is this an inherent limitation to this method or perhaps, as I suspect, my own user error?

The TON_TIME delay is also set to <500ms with no change in limit.
TON_TIME = 0.01 --> ~0.505 sec trigger time
TON_TIME = 0.6 --> ~1.012 sec trigger time

Welcome to the world of non-deterministic operating systems. :slight_smile:

I was able to get a bit better performance by not running it through the alarm subsystem.

UDT_Timer_millis.json (2.1 KB)

2 Likes

Phil,

How much heartburn does this solution give you?

Use system.util.invokeAsynchronous, and then have a time.sleep in that function?

Genuine question. -I'm trying to determine if there is a technical issue doing this.

Thanks,
Ryan

That's a fair use of invokeAsynchronous. The catch to it (and to any non-state-machine solution) is what to do if the source tag toggles rapidly and queues up a bunch of threads, all waiting 30 seconds (or whatever) before performing the rest of the work.

You probably need some check at the end of the sleep to ensure additional transitions haven't occurred. (Rechecking the tag's timestamp, perhaps.)

(I'd probably enqueue a runnable, with delay, onto the gateway context's shared execution manager, not use a dedicated thread. Still have to recheck the condition.)

Excellent suggestion...

Thanks for the response.

Ryan