Opening Sockets to TCP devices using scripting

Hey all,
We've got some machines that will be sending and receiving data to our Ignition using python TCP APIs, and then ideally writing these to tags.

In a testing python prgram that was developed, a socket to the relevant port is opened and made as an object in the code. This socket is then used in an endless while loop to pull the data out in a continuous stream.

Is there a best practice to stream data over a socket like this in Ignition.

We have two options in my mind:

  • Using Gateway Timer Event. Open a socket and request the data, then close that socket before the next Timer loop, or;
  • Have a timer script with a very long timer delay, on a dedicated thread. And just use this endless while loop idea.

We're only pulling over 3/4 JSON objects of data in each cycle, so not massively heavy duty.

What are the risks and/or benefits in the two options.

I'm using your first option with no issues. I also use a ping script on the options with an expression tag to detect if my devices are connected. My application uses devices that are on and off frequently. Although, I never listen for a response back with this method.

I also have a 3rd option, and that's using the TCP device driver in the OPC server. I found that this was the best solution for getting the responses back. Ignition handles the reconnecting and status in the background and the ports are always open/repaired. I send TCP message through scripting and also just write to the message tags of the OPC device tags. Then I use a tag change script on the response tag in the driver and hand the data that way.

1 Like

We'd considered using the TCP device, and I had a quick look but our device has different TCP ports for different categories of commands, I wasn't sure if that would be doable as the Ignition tutorials for the TCP device make it look very basic haha.

Maybe I need to investigate it more, but I'll stick with my option 1 for now and see how it affects performance.

It is very basic. It’s mostly meant for things like barcode scanners and scales that are just blasting back strings at regular intervals.

1 Like

I would think option one should be fine for anything that runs in the once per second range or slower. Consider option two if you expect to make multiple queries per second, but you must make sure your long-lived thread exits gracefully when the hosting script module is reloaded.

Some references (with inner links):

https://forum.inductiveautomation.com/search?q=long-lived%20thread%20persist%20order%3Alatest

1 Like

Use a long lived connection if you need the things that TCP provides. If you can live with UDP then open and close the connection with each message will be easier to make reliable. If you can let someone else handle the connections (i.e., REST endpoints) then do that.

An open socket can fail in lots of ways. Most of those ways are silent. Asking the socket if it is still open and ok is useless - it will happily tell you that it is still connected even though the only thing that is connected is the ethernet to the closest switch. So you end up putting heartbeat messages on both ends to detect broken connections. And you listen on one port but hand that off to another on a connection and then listen on the original port again and if you get another connection while you believe that the first one is still ok then you figure out which is the real client - probably the new one and you close the old (probably dead, which the other end detected and is why it opened another one). Many of the failures are difficult to trigger so that you can test the code that automatically reconnects. And the Nagle algorithm can be a problem if you have small messages so even knowing how to turn that off or if you need to. Lots of OS dependencies.

A new socket per message can run out of ports if you are opening lots of them frequently. In some cases, the OS will not reuse a port for some period after it is closed. I don't remember how many that takes (hundreds per second? more? less?) or if it is relevant for other versions of the OS. Obviously web servers have implemented something differently to not have this issue.

HTH. And thanks for bringing back the nightmares...

1 Like

A very critical piece of advice if you end up opening your own sockets: make sure to use Java standard library imports, adapted to Jython. Resist the temptation to use the Python standard library, especially for socket related code. It's buggy and prone to resource leaks.

5 Likes

Some of our code is already using the python socket library

import socket

etc.

What module should be used instead, and do they work in a similiar way, I guess it won't be as easy as

import java.foo.bar.socket as socket

and then leave our code as is

Thanks!

from java.net import Socket

https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html

So the following code would be considered bad practice?

import socket
import sys

TCP_IP = '192.168.1.100'
TCP_PORT = 17001
CONNECTION_TIMEOUT = 5
		
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(CONNECTION_TIMEOUT)
s.connect((TCP_IP, TCP_PORT))
		
s.send('this is a text message')
				
s.close()

Yes. It's unfortunate, but you're just setting yourself up for failure if you use socket directly.

Chat-GPT translated java.net.Socket equivalent of your snippet, that looks approximately correct:

# Jython Client using java.net.Socket

import java.net.Socket
import java.io.IOException

TCP_IP = '192.168.1.100'
TCP_PORT = 17001
CONNECTION_TIMEOUT = 5000  # The timeout is specified in milliseconds

try:
    s = Socket(TCP_IP, TCP_PORT)
    s.setSoTimeout(CONNECTION_TIMEOUT)

    out_stream = s.getOutputStream()
    message = 'this is a text message'
    out_stream.write(message.encode('utf-8'))

    s.close()

except IOException as e:
    print("Error:", e)
except Exception as e:
    print("Error:", e)
2 Likes

That is funny. I was having ChatGPT do the same thing. I don't know enough about Java though to say if this is particularly good code.

# Import the Java Socket class from the java.net package
from java.net import Socket, InetAddress

TCP_IP = '192.168.1.100'
TCP_PORT = 17001
CONNECTION_TIMEOUT = 5000  # Milliseconds (5 seconds)

try:
    # Create a new Java Socket instance
    s = Socket()
    s.connect(InetAddress.getByName(TCP_IP), TCP_PORT, CONNECTION_TIMEOUT)

    message = 'this is a text message'
    message_bytes = message.encode('utf-8')  # Convert the string to bytes with UTF-8 encoding
    s.getOutputStream().write(message_bytes)  # Send the bytes through the socket's output stream
    s.getOutputStream().flush()  # Ensure the data is sent immediately

except Exception as e:
    print(f"Error: {e}")

finally:
    s.close()  # Close the socket after use

What kind of failure(s) are we talking about?

Memory leaks and socket leaks. On some platforms, most notably Windows from experience, this can lead to complete TCP starvation and an inability to open new ports.