Decode return value

import socket
import time
import sys

HOST = '169.254.63.28'
PORT = 30002

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.settimeout(1)
s.connect((HOST, PORT))
s.send(("get_actual_tcp_pose()"+"\n").encode('utf8'))
message = (s.recv(1024).decode('utf8'))
print(message)

this is the return value:

b’\x00\x00\x007\x14\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x03\tURControl\x05\x0b\x00\x00\x00\x05\x00\x00\x00\x0015-10-2021, 08:11:44\x00\x00\x00\x18\x14\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x01’

Obviously there is a timestamp in it, but I couldn’t decode the rest of it so far.

UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xff in position 5: invalid start byte

hey thanks, so this works quite well

import socket
import struct
import time

HOST = '169.254.63.28'
PORT = 30002

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.connect((HOST, PORT))

while True:
                data = s.recv(4096)
                i = 0
                package_length = (struct.unpack('!i', data[0:4]))[0]
                package_type = (struct.unpack('!B', data[4:5]))[0]
                print("PKG TYPE: " + str(package_type))
                if package_type == 16:
                    while i + 5 < package_length:
                        message_length = (struct.unpack('!i', data[5 + i:9 + i]))[0]
                        message_type = (struct.unpack('!B', data[9:10]))[0]
                        print("MSG TYPE: " + str(message_type))
                        if message_type == 0:
                            sub_package = data[5:i + message_length]
                            is_program_running = struct.unpack('!?', sub_package[18:19])[0]
                            is_protective_stop = struct.unpack('!?', sub_package[17:18])[0]
                            is_robot_on = struct.unpack('!?', sub_package[15:16])[0]
                            time.sleep(0.5)
                            print('PWR ON/STOP/PROG RUN : ' + str(is_robot_on) + '/' + str(is_protective_stop) + '/' + str(is_program_running))
                            break
                        i = message_length + i

I would like to receive other data as well, but I am stuck. This is from another (edited) example but doesn’t return any coordinates. Even when the packettype is 16

#!/usr/bin/env python
# encoding: utf=8
""" 
#UR Controller Client Interface Datastream Reader
# For software version 3.x
#
# Datastream info found here: https://s3-eu-west-1.amazonaws.com/ur-support-site/16496/Client_Interface.xlsx
# Struct library used to extract data, info found here: https://docs.python.org/2/library/struct.html
"""
import socket, struct

def main():

	#Establish connection to controller
	HOST = '169.254.63.28'
	PORT = 30002

	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	s.connect((HOST, PORT))

	while 1:
		#Loop forever, receive 4096 bytes of data (enough to store any packet)
		data = s.recv(4096)
		#initialise i to keep track of position in packet
		i = 0
		if data:
			#print(title to screen
			print('*******')
			print('UR Controller Primary Client Interface Reader')
			print('*******')
			#extract packet length, timestamp and packet type from start of packet and print(to screen
			packlen =  (struct.unpack('!i', data[0:4]))[0]
			timestamp = (struct.unpack('!Q', data[10:18]))[0]
			packtype = (struct.unpack('!b', data[4:5]))[0]
			print('packet length: ' + str(packlen))
			print('timestamp: ' + str(timestamp))
			print('packet type: ' + str(packtype))
			print('*******')

			if packtype == 16:
				#if packet type is Robot State, loop until reached end of packet
				while i+5 < packlen:
					#extract length and type of message and print(if desired
					msglen = (struct.unpack('!i', data[5+i:9+i]))[0] 
					msgtype = (struct.unpack('!b', data[9:10]))[0] 
					#print('packet length: ' + str(msglen)
					#print('message type: ' + str(msgtype)
					#print('*******'
					if msgtype == 1:
						#if message is joint data, create a list to store angles
						angle = [0]*6
						j = 0
						while j < 6:
							#cycle through joints and extract only current joint angle (double precision)  then print(to screen
							#bytes 10 to 18 contain the j0 angle, each joint's data is 41 bytes long (so we skip j*41 each time)
							angle[j] = (struct.unpack('!d', data[10+i+(j*41):18+i+(j*41)]))[0]
							print('Joint ' + str(j) + ' angle : ' + str(angle[j]))
							j = j + 1
					elif msgtype == 4:
						#if message type is cartesian data, extract doubles for 6DOF pos of TCP and print(to sc    reen
						x =  (struct.unpack('!d', data[10+i:18+i]))[0]
						y =  (struct.unpack('!d', data[18+i:26+i]))[0]
						z =  (struct.unpack('!d', data[26+i:34+i]))[0]
						rx =  (struct.unpack('!d', data[34+i:42+i]))[0]
						ry =  (struct.unpack('!d', data[42+i:50+i]))[0]
						rz =  (struct.unpack('!d', data[50+i:58+i]))[0]
						print('X:  ' + str(x))
						print('Y:  ' + str(y))
						print('Z:  ' + str(z))
						print('RX: ' + str(rx))
						print('RY: ' + str(ry))
						print('RZ: ' + str(rz))
					#increment i by the length of the message so move onto next message in packet
					i = msglen + i

if    __name__ == '__main__':
    import sys
    main()

Hey Stefan!

I also tried using this method for recieving robot readings, however, this method is outdated, slow and the code is just a tedious mess imo. Therefor i recommend you to use the python interface on port 30004, it’s the RTDE interface and it is much simpler to use and much easier to pull data readings from the robot.

If you would like, i can show you how to set it up to receive coordinates.

https://www.universal-robots.com/articles/ur/interface-communication/real-time-data-exchange-rtde-guide/

Are you using the ur-rtde package for this? I didn’t manage to install it

Hej again Stefan,
I checked that post to install RTDE and even it shows that it is installed and set up VCode, and stuff, I started encountering the same issues as you. I thought that this RTDE package was based on the same RTDE that is given by UR on the link that @emil.madsen has posted here (the one that I use as well), but it turns out this is some SDU stuff that it might be incompatible.
So, anyway, I came back to this original post, see what you are aiming to do, and found a way:

  1. Leave that ur_rtde post, it’s been dead 8 months already.
  2. Go to the RTDE link given by @emil.madsen , scroll all the way to the bottom, and download the attached files.
  3. Unzip the rtde-2.6.0 folder. In my case, I did this whole thing in a Ubuntu 20 VM.
  4. Create a new file within the example folder and copy-paste the content from record.py. This is how my workspace looks like (00_tcp_example.py is the new file):

rtde_sol1

  1. Modify the new file to get only the info that you need. In this case, I removed the whole printing and just kept the tcp-pose and the timestamp.
#!/usr/bin/env python

import argparse
import logging
import sys
sys.path.append('..')
import rtde.rtde as rtde
import rtde.rtde_config as rtde_config
import time

# parameters
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost',help='name of host to connect to (localhost)')
parser.add_argument('--port', type=int, default=30004, help='port number (30004)')
parser.add_argument('--samples', type=int, default=0,help='number of samples to record')
parser.add_argument('--frequency', type=int, default=125, help='the sampling frequency in Herz')
parser.add_argument('--config', default='record_configuration.xml', help='data configuration file to use (record_configuration.xml)')
parser.add_argument("--verbose", help="increase output verbosity", action="store_true")
parser.add_argument("--buffered", help="Use buffered receive which doesn't skip data", action="store_true")
parser.add_argument("--binary", help="save the data in binary format", action="store_true")
args = parser.parse_args()

if args.verbose:
    logging.basicConfig(level=logging.INFO)

conf = rtde_config.ConfigFile(args.config)
output_names, output_types = conf.get_recipe('out')

con = rtde.RTDE(args.host, args.port)
con.connect()

# settings
con.get_controller_version()
con.send_output_setup(output_names, output_types, frequency=args.frequency)
con.send_start()

# initialize variables
X = 0
Y = 0
Z = 0
RX = 0
RY = 0
RZ = 0
# main loop
i = 1
for i in range(3):
    if args.samples > 0 and i >= args.samples:
        keep_running = False
    try:
        if args.buffered:
            state = con.receive_buffered(args.binary)
        else:
            state = con.receive(args.binary)
        if state is not None:
            X,Y,Z,RX,RY,RZ = state.actual_TCP_pose
            date_and_time = state.timestamp
            i += 1
            print(str(date_and_time)+" TCP: pos ["+str(X)+", "+str(Y)+", "+str(Z)+"] m, rot ["+str(RX)+", "+str(RY)+", "+str(RZ)+"] rad")
            time.sleep(0.1)

    except KeyboardInterrupt:
        break
    except rtde.RTDEException:
        break

con.send_pause()
con.disconnect()

This is the output:

About the Ubuntu 20 VM, you can also use the Ubuntu 16 VM given on the start package from the UR+ page, because the one given with URSim and Polyscope from the main UR page comes with Lubuntu and very basic tools, which makes the Python and pip install harder.

I did setup Ubuntu 20.04.3 from LinuxVMImages.com.
Could you provide me with a simple analog read/write example? (Thats what I am looking for since 3 weeks)
Examples — ur_rtde 1.5.5 documentation
just reads back the state of the digital output?

  • For reading, add these lines on the record_configuration.xml (or whatever other file you’re using for the settings:
<field name=standard_analog_input0 type=DOUBLE/>
<field name=standard_analog_input1 type=DOUBLE/>
<field name=standard_analog_output0 type=DOUBLE/>
<field name=standard_analog_output1 type=DOUBLE/>
<field name=tool_analog_input0 type=DOUBLE/>
<field name=tool_analog_input1 type=DOUBLE/>

So now you can access them from Python. In the sample code I wrote on my previous answer, you can add:

# ...
        if state is not None:
            X,Y,Z,RX,RY,RZ = state.actual_TCP_pose
            date_and_time = state.timestamp
            print(str(date_and_time)+" TCP: pos ["+str(X)+", "+str(Y)+", "+str(Z)+"] m, rot ["+str(RX)+", "+str(RY)+", "+str(RZ)+"] rad")
 
            # new
           analog_IN = [state.standard_analog_output0, state.standard_analog_output1, tool_analog_input0, tool_analog_input1] 
           print(str(analog_IN))

           i += 1
           time.sleep(0.1)
# ... 
  • For writing, I haven’t been able to achieve that part yet (since I started with RTDE very recently). But as an alternative in the mean time I’d use the remote URscript commands interface, located in the port 30002, where you can send URScript commands as messages, and then set an output to a certain value:
import socket

# connect to the robot
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 30002))
# send URscript command: set analog output 0 to 5V or 12 mA
ur_command = "set_standard_analog_out(0,0.5)\n"
s.send (ur_command.encode())
s.recv(680)
s.close()

I don’t use any configuration file, this is my test code, it works quite well

import rtde_io
import rtde_receive
import time

rtde_io_ = rtde_io.RTDEIOInterface("169.254.63.28")
rtde_receive_ = rtde_receive.RTDEReceiveInterface("169.254.63.28")

while True:
    if rtde_receive_.getDigitalOutState(7):
        print("Standard digital out (7) is HIGH")
    else:
        print("Standard digital out (7) is LOW")

    if rtde_receive_.getDigitalOutState(16):
        print("Tool digital out (16) is HIGH")
    else:
        print("Tool digital out (16) is LOW")

    rtde_io_.setStandardDigitalOut(7, True)
    rtde_io_.setToolDigitalOut(0, True)
    time.sleep(1)

    rtde_io_.setAnalogOutputCurrent(1, 0.25)
    time.sleep(1)

    if rtde_receive_.getDigitalOutState(7):
        print("Standard digital out (7) is HIGH")
    else:
        print("Standard digital out (7) is LOW")

    if rtde_receive_.getDigitalOutState(16):
        print("Tool digital out (16) is HIGH")
    else:
        print("Tool digital out (16) is LOW")

    rtde_io_.setStandardDigitalOut(7, False)
    rtde_io_.setToolDigitalOut(0, False)

    rtde_io_.setAnalogOutputCurrent(1, 0.5)
    time.sleep(1)

but I can’t figure out how to read back those analog values

I will try your example later

As soon as I launch this code I get C204A protective stop safety message on the robot. I don’t even understand why the robot starts to move, I can’t see any move commands in the script?

#!/usr/bin/env python

import argparse
import logging
import sys
sys.path.append('..')
import rtde.rtde as rtde
import rtde.rtde_config as rtde_config
import time

# parameters
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='169.254.63.28',help='name of host to connect to (localhost)')
parser.add_argument('--port', type=int, default=30004, help='port number (30004)')
parser.add_argument('--samples', type=int, default=0,help='number of samples to record')
parser.add_argument('--frequency', type=int, default=125, help='the sampling frequency in Herz')
parser.add_argument('--config', default='configuration.xml', help='data configuration file to use (configuration.xml)')
parser.add_argument("--verbose", help="increase output verbosity", action="store_true")
parser.add_argument("--buffered", help="Use buffered receive which doesn't skip data", action="store_true")
parser.add_argument("--binary", help="save the data in binary format", action="store_true")
args = parser.parse_args()

if args.verbose:
    logging.basicConfig(level=logging.INFO)

conf = rtde_config.ConfigFile(args.config)
output_names, output_types = conf.get_recipe('out')

con = rtde.RTDE(args.host, args.port)
con.connect()

# settings
con.get_controller_version()
con.send_output_setup(output_names, output_types, frequency=args.frequency)
con.send_start()

# initialize variables
X = 0
Y = 0
Z = 0
RX = 0
RY = 0
RZ = 0
# main loop
i = 1
for i in range(3):
    if args.samples > 0 and i >= args.samples:
        keep_running = False
    try:
        if args.buffered:
            state = con.receive_buffered(args.binary)
        else:
            state = con.receive(args.binary)
        if state is not None:
            X,Y,Z,RX,RY,RZ = state.actual_TCP_pose
            date_and_time = state.timestamp
            i += 1
            analog_IN = [state.standard_analog_output0, state.standard_analog_output1, state.tool_analog_input0, state.tool_analog_input1] 
            print(str(analog_IN))
            print(str(date_and_time)+" TCP: pos ["+str(X)+", "+str(Y)+", "+str(Z)+"] m, rot ["+str(RX)+", "+str(RY)+", "+str(RZ)+"] rad")
            time.sleep(0.1)

    except KeyboardInterrupt:
        break
    except rtde.RTDEException:
        break

con.send_pause()
con.disconnect()
<?xml version="1.0"?>
<rtde_config>
	<recipe key="out">
		<field name="timestamp" type="DOUBLE"/>
		<field name="target_q" type="VECTOR6D"/>
		<field name="target_qd" type="VECTOR6D"/>
		<field name="target_qdd" type="VECTOR6D"/>
		<field name="target_current" type="VECTOR6D"/>
		<field name="target_moment" type="VECTOR6D"/>
		<field name="actual_q" type="VECTOR6D"/>
		<field name="actual_qd" type="VECTOR6D"/>
		<field name="actual_current" type="VECTOR6D"/>
		<field name="joint_control_output" type="VECTOR6D"/>
		<field name="actual_TCP_pose" type="VECTOR6D"/>
		<field name="actual_TCP_speed" type="VECTOR6D"/>
		<field name="actual_TCP_force" type="VECTOR6D"/>
		<field name="target_TCP_pose" type="VECTOR6D"/>
		<field name="target_TCP_speed" type="VECTOR6D"/>
		<field name="actual_digital_input_bits" type="UINT64"/>
		<field name="joint_temperatures" type="VECTOR6D"/>
		<field name="actual_execution_time" type="DOUBLE"/>
		<field name="robot_mode" type="INT32"/>
		<field name="joint_mode" type="VECTOR6INT32"/>
		<field name="safety_mode" type="INT32"/>
		<field name="actual_tool_accelerometer" type="VECTOR3D"/>
		<field name="speed_scaling" type="DOUBLE"/>
		<field name="target_speed_fraction" type="DOUBLE"/>
		<field name="actual_momentum" type="DOUBLE"/>
		<field name="actual_main_voltage" type="DOUBLE"/>
		<field name="actual_robot_voltage" type="DOUBLE"/>
		<field name="actual_robot_current" type="DOUBLE"/>
		<field name="actual_joint_voltage" type="VECTOR6D"/>
		<field name="actual_digital_output_bits" type="UINT64"/>
		<field name="runtime_state" type="UINT32"/>
        <field name="standard_analog_input0" type="DOUBLE"/>
        <field name="standard_analog_input1" type="DOUBLE"/>
        <field name="standard_analog_output0" type="DOUBLE"/>
        <field name="standard_analog_output1" type="DOUBLE"/>
        <field name="tool_analog_input0" type="DOUBLE"/>
        <field name="tool_analog_input1" type="DOUBLE"/>
<!--
		<field name="payload" type="DOUBLE"/>
		<field name="payload_cog" type="VECTOR3D"/>
		<field name="script_control_line" type="UINT32"/>
		<field name="output_bit_registers0_to_31" type="UINT32"/>
		<field name="output_bit_registers32_to_63" type="UINT32"/>

		<field name="output_int_register_0" type="INT32"/>
		<field name="output_int_register_1" type="INT32"/>
		<field name="output_int_register_2" type="INT32"/>
		<field name="output_int_register_3" type="INT32"/>
		<field name="output_int_register_4" type="INT32"/>
		<field name="output_int_register_5" type="INT32"/>
		<field name="output_int_register_6" type="INT32"/>
		<field name="output_int_register_7" type="INT32"/>
		<field name="output_int_register_8" type="INT32"/>
		<field name="output_int_register_9" type="INT32"/>
		<field name="output_int_register_10" type="INT32"/>
		<field name="output_int_register_11" type="INT32"/>
		<field name="output_int_register_12" type="INT32"/>
		<field name="output_int_register_13" type="INT32"/>
		<field name="output_int_register_14" type="INT32"/>
		<field name="output_int_register_15" type="INT32"/>
		<field name="output_int_register_16" type="INT32"/>
		<field name="output_int_register_17" type="INT32"/>
		<field name="output_int_register_18" type="INT32"/>
		<field name="output_int_register_19" type="INT32"/>
		<field name="output_int_register_20" type="INT32"/>
		<field name="output_int_register_21" type="INT32"/>
		<field name="output_int_register_22" type="INT32"/>
		<field name="output_int_register_23" type="INT32"/>

		<field name="output_double_register_0" type="DOUBLE"/>
		<field name="output_double_register_1" type="DOUBLE"/>
		<field name="output_double_register_2" type="DOUBLE"/>
		<field name="output_double_register_3" type="DOUBLE"/>
		<field name="output_double_register_4" type="DOUBLE"/>
		<field name="output_double_register_5" type="DOUBLE"/>
		<field name="output_double_register_6" type="DOUBLE"/>
		<field name="output_double_register_7" type="DOUBLE"/>
		<field name="output_double_register_8" type="DOUBLE"/>
		<field name="output_double_register_9" type="DOUBLE"/>
		<field name="output_double_register_10" type="DOUBLE"/>
		<field name="output_double_register_11" type="DOUBLE"/>
		<field name="output_double_register_12" type="DOUBLE"/>
		<field name="output_double_register_13" type="DOUBLE"/>
		<field name="output_double_register_14" type="DOUBLE"/>
		<field name="output_double_register_15" type="DOUBLE"/>
		<field name="output_double_register_16" type="DOUBLE"/>
		<field name="output_double_register_17" type="DOUBLE"/>
		<field name="output_double_register_18" type="DOUBLE"/>
		<field name="output_double_register_19" type="DOUBLE"/>
		<field name="output_double_register_20" type="DOUBLE"/>
		<field name="output_double_register_21" type="DOUBLE"/>
		<field name="output_double_register_22" type="DOUBLE"/>
		<field name="output_double_register_23" type="DOUBLE"/>
	-->
	</recipe>
</rtde_config>

The script only reads the joints and analog I/O from the robot. I have run it on my VM with URSim and no protective stop has happened to me.

Is your robot on Automatic or Remote Mode? To me it sounds that you might have something else running on the background.
Try to run the code with the robot paused in manual mode.

I can’t run the script without remote control. There ist no script loaded on the robot.

ur_rtde: Please enable remote control on the robot!

I did freedrive the robot arm to a different position and it starts moving as soon as I run the script I posted above in vscode. It can’t reach the desired position altough thats why it fails. I don’t know why the arm starts moving in the first place.

edit: I did set a breakpoint just before import rtde.rtde_config as rtde_config and the unexpected movement stopped. So there has to be some sort of standard position in the configuration?

Ok, a few things:

  • The script that uses the record_configuration.xml file and the one that starts with import rtde_io, they both process the RTDE info from the robot in different ways. You can run them independently.
  • The one with import rtde_io uses the package given by SDU, and is the one that sets signals on the robot.
  • The one with the record_configuration.xml file is based on an example given by UR and it ONLY reads signals from the robot and it does not require the robot to be in remote control in order to access the info from the arm. My suggestion was that you run only this script alone and WITHOUT the import rtde_io and other dependencies from the SDU package.

Are you running the scripts separately or have written another script that mixes both?

https://forum.universal-robots.com/t/decode-return-value/18631/10 is the only script I am running

ur6
I did set breakpoints… as soon as rtde.rtde gets imported the robot starts moving

Ok, then check the robot side, is there a URCap from the SDU package active? Could you disable or uninstall it temporally to re-run the script?

The only thing active on the robot side is a safety plane and an URcaps from the zimmer gripper

Then it might be that there are 2 rtde.py files (one from SDU package and the one used on this script) and something gets messed up while importing. So the scripts uses this workspace:

And the dependencies are inside rtde-2.6.0/rtde folder. Could you try to rename and re-factor the rtde.py there? Like rename it to rtde_reading.py and then from the main change it to

import rtde_reading.rtde as rtde
import rtde_reading.rtde_config as rtde_config

(and also change the import rtde... in any other files if needed)

EDIT: at this point I’m just guessing because I cannot recreate the error on my computer.

I did rename the folder to rtde2 and modified the code accordingly, it reads back everything like it should, but the arm still wants to move! :see_no_evil:

simplified testcode:

import argparse
import sys
sys.path.append('..')
import rtde2.rtde as rtde2 #robot starts moving unexpected
import rtde2.rtde_config as rtde2_config

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='169.254.63.28',help='name of host to connect to (localhost)')
parser.add_argument('--port', type=int, default=30004, help='port number (30004)')
parser.add_argument('--samples', type=int, default=0,help='number of samples to record')
parser.add_argument('--frequency', type=int, default=125, help='the sampling frequency in Herz')
parser.add_argument('--config', default='configuration.xml', help='data configuration file to use (configuration.xml)')
parser.add_argument("--verbose", help="increase output verbosity", action="store_true")
parser.add_argument("--buffered", help="Use buffered receive which doesn't skip data", action="store_true")
parser.add_argument("--binary", help="save the data in binary format", action="store_true")
args = parser.parse_args()

conf = rtde2_config.ConfigFile(args.config)
output_names, output_types = conf.get_recipe('out')

con = rtde2.RTDE(args.host, args.port)
con.connect()

con.send_output_setup(output_names, output_types, frequency=args.frequency)
con.send_start()

state = con.receive(args.binary)
analog_IN = [state.standard_analog_output0, state.standard_analog_output1, state.standard_analog_input0, state.standard_analog_input1, state.tool_analog_input0, state.tool_analog_input1] 
print("Analog: " + str(analog_IN))

con.send_pause()
con.disconnect()

now it runs like it should but without ubuntu/wsl, I don’t know why

EDIT: How to figure out if analog is in current or voltage state while reading back?

Ok, I’m glad you could figure it out. I’m just surprised that it didn’t work in Ubuntu. Maybe some package or internal dependency differs from your computer to mine.
According to the script manual, the analog input reading depends on the settings that you have set on the I/O (installation tab):