I made a simple Update Gateway Event which had a while loop inside of it and rather than killing the current while loop and starting a new one when you update the project. It puts that while loop in the background and creates another one so you essentially have two.
There is also no good way to stop these loops (that I know of) once they start so you're forced to change the script to not include a loop and then restart the gateway.
I was wondering if this is something that would ever be fixed in the future so that upon the Gateway event project update it kills the current running code entirely.
Here's some code I wrote to demonstrate this. The inside the if statement one prints 10 times and then the outside one keeps going forever. Even upon saving or updating the "broken while loop" is never triggered.
I also tried completely replacing the code with just print("test") with no while loop then saving and the outside if statement keeps going. Also if you do a thread dump on the gateway you can see this process still running. if you save a bunch of times it eventually bogs down the gateway and you need to manually kill it and restart it.
Generally speaking though, there is no place where you can safely write infinite loops. Thread.sleep / time.sleep is also anywhere from not encouraged to detrimental, depending where you're doing it.
I feel like for components and stuff that makes sense to discourage while loops but would this ever be fixed for stuff like gateway events and or project library scripts?
Also is there and way I could write this while loop to break upon an update or detect an update state before it happens?
There's nothing to "fix" because I'm not sure it's not broken. We don't cancel scripts that happen to be running when the scripting lifecycle restarts, and we wouldn't be able to tell the difference between a "legitimate" script that is about to finish and one that is never going to finish.
To be clear: use while loops all you want. But you can't write ones that never end, and you can't expect your thread to get killed for you.
Maybe you should be using a timer script to accomplish whatever it is you're trying to do?
What I'm using it for is already not an ideal approach but it is working very well so far.
We have a machine with a linear encoder and we want to very quickly be able to read a value from that linear encoder and write it to a tag.
We've tried Modbus TCP/IP (slow)
Serial (slow because our HMI's run in virtual machines we VNC to)
and this UDP approach I wrote just for testing
def startFunction():
import socket
import system
UDP_PORT = 5175
sock = None
try:
if sock:
print("Closing existing socket")
sock.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", UDP_PORT))
sock.settimeout(1)
print("Listening for UDP data on port", UDP_PORT)
while True:
try:
data, addr = sock.recvfrom(1024)
value = float(data.decode('utf-8').strip())
print("Received from", addr, ":", value)
system.tag.writeBlocking(
['[TagProvider]EncoderBoards/MachineName/encoderBoardValue.value'],
[value]
)
except socket.timeout:
pass
except Exception as e:
print("Error reading from socket:", str(e))
except Exception as e:
print("Failed to bind UDP socket:", str(e))
finally:
if sock:
sock.close()
print("Socket closed")
startFunction()
I realized another issue I had with the code and solve the while loop issue so that doesn't matter anymore
But do you have any other suggestions for how I can quickly update a tag? my options are pretty open as I'm also programming our custom PCB to read the value from the linear encoder
Your approach would be fine in an asynchronous thread. Events in Ignition are not supposed to run long--no more than a few milliseconds normally. Running long ties up a thread that usually has multiple duties.
You should be using system.util.invokeAsynchronous() in a gateway startup event to kick off this function. (Which should be defined in a project library script, not def'd in the event itself, for complicated reasons.) If you go this route, you will also need to program your loop to properly suicide on project edits:
this is actually what I had initially which made me believe that while loops was the issue. It was actually that I was using invokeAsynchronous and when it ran it would create a new thread that I was unable to kill and updates didn't kill.
If you go this route, you will also need to program your loop to properly suicide on project edits:
This must be what I was missing because the threads would just build up and kill my gateway. Is there an easy way to detect a project update and kill the thread?
Consider configuring this to store the last x reads with UTC timestamps in a ring buffer and then offload it in chunks at a lower rate (1s, 5s). If you don't care about the real-time data and just need to be able to plot the reads, this should be enough.
You can put the most recent value in a batch a display tag. Or put the entire batch in a dataset tag.
the only value we need is the most recent. So If I send two packets back to back I don't really care about the first one that was sent only the most recent.
It just needs to display the value to the HMI user quickly and we don't care about previous data. Just as long as they can see the current value with as minimal latency as possible
I usually use my system.util.globalVarMap() to hold objects that need to survive project edits. (It can be the native system.util.getGlobals() also, but there are quirks.) It is vital that only native java or jython object types go in there, unless your scripts are smart enough to clean old stuff out. I put the running thread's instance object in there, and the new one retrieves it, interrupts it, and replaces it in that function started from the gateway startup event.
this seems like a good idea I'll write that down and look into that
I was also looking at going the OPC UA route and having my board communicate with ignition that way
my only concern is we used Modbus TCP/IP which was fairly slow and OPC UA also uses TCP so that's why I switched to UDP
we also don't really care about the three way handshake with TCP and making sure that packets arrive safely because it would be pretty obvious if there was an issue
The TCP listener class I made here for listening to barcode scanners might be of some use. I was offloading to a queue for another script to chew through, you'll just be writing to a tag. I think there is also a listener server class in the thread somewhere, I was anticipating barcode scanners that acted as clients as well.
Further up in the post it talks about the storing of the old instance and killing it on script environment update/reset.
I'll have a look at this too! Would you happen to know how much of a latency difference I would get between UDP and TCP? I know UDP is generally faster and has a smaller feature set (which is fine) but I don't know how much slower TCP would be
It's a chop saw where you move the end stop and it outputs the value to the screen. And that value on the screen is the calculated length of the piece you are cutting.
So the value updates don't need to be instant but I went down and tried out the machine and it's just very odd with latency. And if you're trying to get a very precise value and move it slightly the latency doesn't help because you tend to overshoot where you move the saw stop