Send joint positions as string via TCP/IP

Hi there,

I’m trying to send joint positions as a string via a socket connection to a python script I have. I’m having trouble sending stringified joint positions via TCP communication.

writer.appendLine("thread PathRecorderThread():");
writer.appendLine("  socket_open(\"127.0.0.1\", 40405, \"daemon\")");
writer.appendLine("  while (True):");
writer.appendLine("    q = get_actual_joint_positions()");
writer.appendLine("    msg = to_str(q)");
writer.appendLine("    socket_send_string(msg, \"daemon\")");
writer.appendLine("    sync()");
writer.appendLine("  end");
writer.appendLine("  socket_close(\"daemon\")");
writer.appendLine("end");

The python script receives strings, stores them in a buffer, and when the buffer is full dumps it.
Here’s a snippet of my script:

def listen_for_string(ip, port):
    global last_timestamp
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((ip, port))
    sock.listen(1)
    
    print("Daemon listening on {}:{}".format(ip, port))

    while True:
        connection, client_address = sock.accept()
        remainder = "" # Buffer to hold partial messages
        try:
            while True:
                data = connection.recv(1024)
                if not data:
                    break 
                combined = remainder + data.decode('utf-8')
                parts = combined.split(']')
                remainder = parts.pop()

                for p in parts:
                    if '[' in p:
                        raw_values = p.split('[')[-1] 
                        
                        try:
                            joint_array = [float(x) for x in raw_values.split(',')]

                            current_timestamp = datetime.now()
                            delta_t = 0
                            if last_timestamp is not None:
                                delta_t = int((current_timestamp - last_timestamp).total_seconds() * 1000000)
                            last_timestamp = current_timestamp

                            entry = {
                                "delta_t": delta_t,
                                "joints": joint_array 
                            }

                            with ring_lock:
                                ring_buffer.append(entry)
                                if len(ring_buffer) >= RING_SIZE:
                                    threading.Thread(target=dump_to_json).start()
                        except ValueError:
                            continue

If anyone can see why my socket communication is not working, please let me know.

Thanks,
Mattis

Can you provide more information about “not working”? Does it do nothing at all, or do you get incorrect data readings, or…?

It does nothing at all from what I can tell: Returns false due to failure.

When I put a counter in the same loop which would increment each time the socket sent a string, the counter updated very slowly, which I believe means that the socket request blocks the thread, then times out.

My aim is to sample joint positions in the background and stream them very quickly to a python server via this socket.

So I see where you’re defining the thread, but are you ever actually running it?

Yes, I am. I run the thread, then writeChildren(), then kill the thread.

I’ve also tried just sending one string in the same way as an example I found on the forum, only to get the same result. This is how my generateScript looked then:

// writer.appendLine("thread PathRecorderThread():");
// writer.appendLine("  socket_open(\"127.0.0.1\", 40405, \"daemon\")");
// writer.appendLine("  while (True):");
// writer.appendLine("    q = get_actual_joint_positions()");
// writer.appendLine("    socket_send_string(to_str(q), \"daemon\")");
// writer.appendLine("    sync()");
// writer.appendLine("  end");
// writer.appendLine("  socket_close(\"daemon\")");
// writer.appendLine("end");

// start thread, then write children nodes
// writer.appendLine("recorder_thread_handler = run PathRecorderThread()");
writer.appendLine("socket_open(\"127.0.0.1\", 40405, \"safe-restart\")");
writer.appendLine("sent = socket_send_string(\"[1,2,3,4]\", \"safe-restart\")");
writer.appendLine("socket_close(\"safe-restart\")");

writer.appendLine("if not sent:");
writer.appendLine("  popup(\"Unsuccessful send.\", \"Safe Restart\", blocking=True)");
writer.appendLine("end");
writer.writeChildren();
// stop recording when program exits node
// writer.appendLine("kill recorder_thread_handler");

The commented code is what I had before.

Start even simpler. You’ve got your python script doing quite a bit of stuff. When I hit the “nothing is working” state, it usually helps me to dial way back. For example, sending an array is a step further than I would go at this point. Can you send a single piece of text or a single number? Don’t try to parse it into something meaningful yet, just print it to the screen.

In my experience, the socket communication on the UR is fairly slow. At least when piping it back to a URCAP. That’s what the RTDE is for. For applications like yours, where you’re trying to stream joint positions to python, I see most people go the RTDE route. Which is where I would have to leave you with a wave and a “good luck” since I’ve never been able to get that working properly.

Currently I’m using and XML-RPC daemon to send the position information.

According to this page, socket communication should definitely be faster than XML, and due to the limitations of my project, I need to stick with local port communication (i.e. can’t use RTDE).

I’m using this thread to set up my socket communication.

I’ll take your advice and try scale my script back, but I don’t see what I’m doing wrong. The array parsing is straight from the above example.

If you are trying something similar, make sure you read the documentation.

Daemons are by default off upon construction, so you need to explicitly turn them on somewhere (or in the constructor of your service class).

1 Like

I’m glad you’ve figured out your problem, but I would recommend using RTDE for monitoring things like joint angles - it requires nothing at all in your robot script as the interface is there and working anyway. The UR sample RTDE client for Python has always worked well for me; just update the recipe file to specify that you want the joint angles, and run the sample “record” script and voila you have a CSV of joint angles.

I’ve seen elsewhere on the forum that the RTDE interface supports up to three concurrent connections. Do you know if that’s true?

I know it supports more than one, as I have used two myself. I don’t think I’ve tried more than that, though, so can’t say beyond that.

1 Like

Are you able to show me your python script? I can’t get the sampling to work.

I assume you’re running the script as a normal daemon and have the /rtde/ folder in you resources containing the csv and rtde scripts?

I just use the record.py script in the sample client package like

python record.py  --host 192.168.198.133 --frequency 500 --buffered

after setting up what fields I want in the record_configuration.xml file. The data is written to robot_data.csv in the examples/ directory. That can be altered by another command-line option, though. And if I were making this into a proper production script I would instead use the sample as guidance and write something more specific, but the principle is all there.

What works and doesn’t work for you? I’m not sure what you mean by “I can’t get the sampling to work”.

I’m trying to adapt the RTDE package to pull joint positions to a locally running python daemon, then send joint positions to a csv (or json) in a local /programs/…/ folder. Currently I have the default daemon implementation from the SDK examples wired up to the record.py script. I start it normally through the Java DaemonService class with no additions:

// IN THE DAEMONSERVICE CLASS
@Override
public URL getExecutable() {
	try {
		return new URL("file:com/Company/DaemonCapt/impl/record.py");
	} catch (MalformedURLException e) {
	 	return null;
	}
}

// IN A URCAP INSTALLATION
daemonService_.startDaemon();

First of all, is this reasonable/possible? Secondly, if so, am I missing some arguments to pass in? I’ve started and stopped daemons through the installation before, so I see no reason why I shouldn’t be able to do this.

I didn’t change anything else (to do with paths as well) because I assumed the daemon would put the csv somewhere visible in the file manager, but I cannot see any csv file.

I think this is probably reasonable, though you may want to make some tweaks to it once you’ve got the fundamentals going. Do you also have the record_configuration.xml file installed with your URCap? The script relies on that file to determine what RTDE fields to request. Other than that, the default values for all the arguments are reasonable for a starting point so it should work without them and write data out to robot_data.csv in the same directory as the script. I’m not sure where the URCap puts the script, I assume you’ve found that?

If it’s not working, I would start by trying to run the script manually, as that way you will get to see any error messages more easily.

Ok, I will update the thread once I’ve figured out how to do it.

Thanks for your help!