Delay in TCP/IP communication

Hello,

As a forewarning: I am new to Python programming and very inexperienced with programming in general, which includes Ignition.

I am attempting to create a crude program to test the validity of communicating with a small robot arm over TCP/IP. I have successfully sent and received data, but there is a delay of approximately 20-30 seconds between my button press (triggering my script) and action from the robot arm and a flurry of received data. I have tested TCP/IP communication with PuTTY, and that is immediate, while a separate Python program I wrote (very basic) has almost no delay after I enter something in the command line.

I have seen in other posts here that importing Python libraries to manage TCP/IP socket communication is not advised, which is what I am doing*. Is this the reason for the large delays? I spoke with a coworker and they suggested I first try sustaining a socket rather than opening one, sending/receiving data, and then closing it for every unique command. I'm hoping someone here might know the more likely culprit for the delays.

The send data is something like "ActivateRobot", "GetCheckpoint", or "StartProgram(##)" while the received data is [####][##] or [####][Connected to robot R## v##.##]

*I made an attempt to convert the python to java but ran into formatting issues.
The send and receive data is relatively small.

Thank you for your time!

You'll need to share your code to get some feedback.

I think it's likely that at least some of your problem is that by doing this in a button press you're executing blocking code on the UI thread, and your UI freezes until it finishes. But we'll need code to help you.

Hmm, I am writing the responses from the robot to a memory tag using system.tag.writeBlocking(). Something else I found in my searches through the posts here. I'm writing the responses to a memory tag so that I can see them in a separate text box. That is the idea anyway.

I'm going to try system.tag.write() (just looked it up, should have done so before) and see where I get from there. I will respond with my progress.

The buttons are not mandatory. I just need a way to store the responses so that I know, should we have a professional program this in the future, they can receive a confirmation from the robot that indicates the robot finished a particular work cycle. The display of the response is just for my QOL and debugging.

First button.

import sys
import socket
import time

#create an INET, STREAMing socket (IPv4, TCP, TCP/IP)
try: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
	SocketMsg = 'Failed to create socket'
	system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg]) #writing to memory tag value
	sys.exit()

SocketMsg = 'Socket created'
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])

RobotIP = "IP_number"
RobotPort = 10000
#Connect the socket object to the robot using the IP address (string) and port (int)
client.connect((RobotIP,RobotPort))

SocketMsg = 'Socket connected to ' + RobotIP
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])
#Read the response sent by the robot upon connecting
SocketMsg = client.recv(1024).decode('ascii')
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])


client.close()
sys.exit()

second button:

import sys
import socket
import time

#create an INET, STREAMing socket (IPv4, TCP, TCP/IP)
try: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
	SocketMsg = 'Failed to create socket'
	system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])
	sys.exit()
	
SocketMsg = 'Socket created'
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])

RobotIP = "192.168.0.42"
RobotPort = 10000
#Connect the socket object to the robot using the IP address (string) and port (int)
client.connect((RobotIP,RobotPort))

SocketMsg = ('Socket connected' + RobotIP)
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])
#Read the response sent by the robot upon connecting
SocketMsg = client.recv(1024).decode('ascii')
system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])

#send command to activate the robot
cmd = 'ActivateRobot'
#add ASCII NULL character at the end of the command string
try:
	client.send(bytes(cmd+'\0' + 'ascii'))
	time.sleep(15)
	SocketMsg = client.recv(1024).decode('ascii')
	system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])
except socket.error:
	SocketMsg = 'Failed to send data'
	system.tag.writeBlocking(["[default]Meca500_TCP/SocketMsg.value"],[SocketMsg])


client.close()
sys.exit()

Thanks!

Use system.tag.writeAsync instead, system.tag.write is deprecated.

Don't ever use sys.exit() in Ignition.

Define these button scripts as functions in the top level scripting library for the project. Then instead of sys.exit() use return. Call these functions in the relevant button action scripts.

Consider using a logger for the basic 'Robot connected', 'robot connection failed', etc messages, and only write the actual robot response to the tag. Loggers show up in the bottom of the designer by default, and you can open a menu to see the logs in a vision client.

How fast are you planning on talking to this robot? You might be able to get away with making it a TCP device on your gateway.

For a quick 'Fire and forget' command method, just to see if you can get the robot to start quickly without looking for a response:

from java.net import Socket
from java.io import DataOutputStream
from java.lang import Throwable

logger = system.util.getLogger("RobotConnectionProject." + __name__)


def sendRobotCommandViaSocket(command, robotIP, robotPort):
	if not robotIP:
		logger.warn("Unable to send command to robot, robot IP not specified")
		return False

	if not robotPort:
		logger.warn("Unable to send command to robot, robot port not specified")
		return False

	try:
		robotSocket = Socket(robotIP, robotPort)
		robotOutputStream = DataOutputStream(robotSocket.getOutputStream())
		robotOutputStream.write(command)

		robotOutputStream.close();
		robotSocket.close();

	except Throwable as t:
		logger.warn("Java error while attempting to send robot command via socket", t)
		return False

	except Exception as e:
		logger.warn("Jython error while attempting to send robot command via socket")
		return False

	return True

Lines like these are going to freeze your UI. The sleep is especially bad because your UI will freeze for 15 seconds every time on that line.

You should get a packet capture with Wireshark. See if there's any delay between your button press and the request being sent, and between the request and response.

2 Likes

Hello Ryan,

I am running into difficulties with system.tag.write() right now, so thank you for the tip about it being deprecated. I scrolled down past the big yellow header saying so in my race to use it.

Thank you for the notice about sys.exit(). Not in my defense of myself or to throw someone else under the bus, but nearly all of this came from the robot API example, which was specifically written for Python. I figured it wasn't going to be ideal, but it was a starting point.

Both robot ports are already on the TCP gateway. I can see their responses when I enable them on the gateway. However, when I enable them on the gateway, the robot refuses any new socket connections as it only supports one. I looked up joining into a socket, but that it far and above my skill level.

As far as communication rates, each robot subroutine takes a few seconds, so if we can stay ahead of that then we're good. We use PLCs a lot, so we're familiar with 100-200 millisecond scan times (not that we utilize those rates, mind you). If sending messages and expecting responses in the neighborhood of once a second or less is untenable, then we will probably stick with PLCs.

And thank you for the code example, I appreciate that!

Hello Kevin,

15 seconds?! I figured it was milliseconds since that makes for easy integers.

Maybe I am not understanding the forum formatting completely, but I am surprised that SocketMsg = client.recv(1024).decode('ascii') would cause a delay. That is required to set the maximum message size and how it is to be encoded, correct?

I have wireshark and have used it before - I will utilize it here.

Thank you for your input, I appreciate it.

Welcome to programming in Python :slight_smile:

https://docs.python.org/2.7/library/time.html#time.sleep

I should say... it can block. It will block until at least one byte is received. It will return at most 1024 bytes the way you're calling it.

But there's no guarantee about how many bytes are returned; it returns as many as are available. That could be 1, 2, 20, or all of them. TCP is a streaming protocol, and data sent via TCP typically has to be framed in some manner so that the receiving side knows when it's received the entire message.

I suspect that may be why you were messing around with a sleep call...

https://docs.python.org/2.7/library/socket.html#socket.socket.recv

Ah ok, the blocking until something is received makes sense.

Not to defend my poor programming, but the sleep call was part of the example TCP/IP program from the robot manufacturer. I started with some of the examples in prior forum posts here but was unable to adapt them to my needs, and then started looking to see if the manufacturer had anything. It does make sense why they would start there though, that way they ensure you receive a message when you're just testing. Later when you run into time delay issues you start a forum post for a different product because surely the issue lies there.

Thank you for all of the resources, I will try to to implement them and improve my attempts.

They most likely didn't take into account that doing this hangs up the same thread that manages the UI of the Perspective session (edge case). In most cases it would probably be ok.

You should be able to configure them to also allow writes, and send your commands via that tag.

Are you trying to drive the steps of your process using Ignition instead of something like PLC at the station?

I don't think the TCP driver is going to be a good fit for this. It sounds like the response messages are going to be too variable.

It's really only a good option when you are talking to something like a barcode scanner or scale that is going to spit out very regular fixed-length or delimited messages.

1 Like

I kind of figured, but was hopeful that they would not need to get into long lived socket threads.

I will review the gateway port communication to see if I can adjust them appropriately.

We have already established a working process with a PLC (the PLC calls stored subroutines on the robot memory), but we wanted to see if we could bypass all logic on the PLC and use it as an I/O rack while Ignition does the thinking. We have encountered some customers who prefer this method so we like to explore this option when possible.

For the most part it is regular, typically [####][##]. The welcome message is a longer string though. But, this exercise was to find out these issues specifically and determine how appropriate Ignition (at least TCP/IP communication) might be in this situation. I will explore some of the options and tips mentioned above though to see how close they might get me.

If you have larger hopes for this communication than just a few tests, you will need to re-architect the whole thing. As Kevin alludes above, you simply cannot do blocking (sleeping or waiting) operations in a Vision Client event.

You will likely need to study the topics here that describe what is needed to run long-lived threads (if you absolutely must use linear code), or learn to use timer events to run short chunks of code in a state machine.

A machine manufacturer chunk of standalone sample code is not transferable to Ignition's event-driven code model.

2 Likes

This is absolutely just for a few tests. We have others who are better versed with software here, and when we need serious solutions, we contract with a team of of dedicated software engineers.

I am in the process of studying all of the responses here as it not only helps with this task but helps in the future.

I didn't mean to imply that the sample code from the manufacturer would be sufficient for an actual solution. I read through a number of TCP/IP posts here (a number of them featuring your responses, including not using import socket) and tried to adapt the solutions proffered there but was unable to get them working due to my lack of knowledge. Fortunately for me, and unfortunately for everyone here, the sample code at least got me responses from the robot, so it was/is my starting point.

If you had a manual or documentation or could otherwise accurately describe the response message format you might get a few more hints from people.

Before I take up more of everyone's time here, I will work on the suggestions people have posted here as well as what my own research turns up. The intent with my original post was not to co-opt everyone's time here to solve my issue, but to see if my timing issues were more likely to be python related or opening/closing socket related so that I could then pick the most probable path to research. Looking back, I see I could have been more clear in that.

However, I would like to stress that I greatly appreciate everyone's help and input on this matter.

2 Likes

No worries. We're a bunch of nerds here. :grin:

1 Like