Attempting to use RTDE interface via XML-RPC communication, Polyscope GUI stops responding!

Hi Everyone,

I have prepared a urcap titled “ CapSens ” which consists of a Daemon (Python script running as an executable in the background) process along with the Program Node and Installation Node. Inside the Daemon process specific Python script the following tasks are performed.

Task 1 => Acquiring data from the sensors
Task 2 => Setting a certain general purpose boolean input register in the robot control unit to 1 or 0 depending upon the acquired data from the sensors

To do the above-mentioned tasks, i implemented two functions. To perform task 1, i have implemented the function “acq_loading_cycles()” and to perform task 2 i have implemented the function “rtde_freedrive()”. The Daemon process specific Python script which contains these functions registered as methods to the XMLRPC server is as follows.

#!/usr/bin/env python

import os
import sys
import time
import string
import traceback
import logging

import socket
import struct
from time import sleep

# seed the pseudorandom number generator
from random import seed
from random import random

import xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer 
import threading 
from threading import Thread, Lock 

# [RTDE](https://www.universal-robots.com/articles/ur-articles/real-time-data-exchange-rtde-guide/)
import rtde.rtde as rtde 
import rtde.rtde_config as rtde_config 


urHost = "127.0.1.1" # UR robot's Offline Simulator IP address 
# [Remote Control via TCP/IP](https://www.universal-robots.com/articles/ur-articles/remote-control-via-tcpip/)
urPort = 30004 # Port number dedicated for UR robot's secondary interface 
config_filename = 'control_loop_configuration.xml' 

logging.basicConfig(level=logging.DEBUG) 
rtdeLogger = logging.getLogger("rtde") 

rtdeConn, controller_info = None, None 
rtde_conn_ok, rtde_comm_started, recept_ok, keep_running = False, False, False, False 
state_names, state_types, set_freedrive_name, set_freedrive_type = None, None, None, None 
set_freedrive = None 
dataAccessConfig = Lock() 
mutexFreedriveMode = Lock() 
rtde_stop_flag = False 


sensEvalBoard_ipAddr = "" # IP address of sensor evaluation board 
sensEvalBoard_port = 0 # Port number of  of sensor evaluation board 
DEFAULT_TIMEOUT = 1 
sebSocket = None # initializing the socket object for sensor evaluation board 
sebSocketConnState = 0 
send_data_on_socket_flag, receive_data_on_socket_flag = False, False
mutexSEBSocket = Lock() 

mutexSensVal = Lock() 
loadCycleOfSensor1, loadCycleOfSensor2, loadCycleOfSensor3 = "", "", "" 
c1, c2, c3 = 0, 0, 0 
inputData, receivedData = None, None 
sensevalboard_stop_flag = False 
cnt = 0 

title = ""
tmp = ""

def set_title(new_title):
	global title
	title = new_title
	return title

def get_title():
	tmp = ""
	if str(title):
		tmp = title
	else:
		tmp = "No title set"
	return tmp + " (Python)"

def get_message(name):
	if str(name):
		return "Hello " + str(name) + ", welcome to PolyScope!"
	else:
		return "No name set"


def set_ipaddr(new_daemon_host): 
    global sensEvalBoard_ipAddr	
    sensEvalBoard_ipAddr = new_daemon_host 
    return sensEvalBoard_ipAddr

def get_ipaddr():
	tmp = ""
	if str(sensEvalBoard_ipAddr):
		tmp = sensEvalBoard_ipAddr
	else:
		tmp = "No ip address set"
	return tmp

def set_portnum(new_daemon_port): 
    global sensEvalBoard_port 
    sensEvalBoard_port = int (new_daemon_port) 
    return sensEvalBoard_port

def get_portnum():
	tmp = 0
	if sensEvalBoard_port != 0:
		tmp = sensEvalBoard_port
	else:
		tmp = 0
	return tmp


def set_led(sid, val): 
    global inputData 
    
    inputData = bytearray() 
    inputData.append(0x2) 
    inputData.append(int (sid)) 
    inputData.append(int (val)) 

    send_dataOnSocket(inputData)
    
    
def set_rgb(sid, r, g, b): 
	global inputData
	
	inputData = bytearray()
	inputData.append(0x4)
	inputData.append(int (sid))
	inputData.append(int (r))
	inputData.append(int (g))
	inputData.append(int (b))
	
	send_dataOnSocket(inputData)

class ConnectionState:
    DISCONNECTED = 0
    CONNECTED = 1
    STARTED = 2
    PAUSED = 3

def connect_socket(): 
    global sebSocket 
    global sebSocketConnState 

    try:
        sebSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # sebSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # sebSocket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        sebSocket.settimeout(DEFAULT_TIMEOUT)
        sebSocket.connect((sensEvalBoard_ipAddr, sensEvalBoard_port))
        sebSocketConnState = ConnectionState.CONNECTED
    except (socket.timeout, socket.error):
        sebSocket = None
        raise

def send_dataOnSocket(data): 
    global sebSocket 
    global send_data_on_socket_flag 
    
    # client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # client_socket.connect((daemon_host, daemon_port))
    # client_socket.send(data) 
    # client_socket.close()

    if sebSocket != None: 
        sebSocket.send(data) 
        send_data_on_socket_flag = True 
    else: 
        send_data_on_socket_flag = False  
        print("socket connection not established")
        print("socket reconnecting")
        connect_socket() 
    
    return send_data_on_socket_flag

def receive_dataOnSocket(data): 
    global sebSocket 
    global receive_data_on_socket_flag 
    global receivedData 
    
    # client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # client_socket.connect((daemon_host, daemon_port))
    # client_socket.send(data);  
    
    # receivedData = client_socket.recv(1024)
    # client_socket.close()

    if (send_dataOnSocket(data)): 
        receivedData = sebSocket.recv(1024) 
        receive_data_on_socket_flag = True
    else: 
        receivedData = None
        receive_data_on_socket_flag = False 
        print("socket connection not established")
        print("socket reconnecting...")
        connect_socket() 
    
    return receivedData

def get_loadingCycleOfCapSen1(): 
    global loadCycleOfSensor1
    
    return loadCycleOfSensor1

def get_loadingCycleOfCapSen2():
    global loadCycleOfSensor2 
    
    return loadCycleOfSensor2

def get_loadingCycleOfCapSen3():
    global loadCycleOfSensor3 
    
    return loadCycleOfSensor3

def acq_loading_cycles():
    global cnt, loadCycleOfSensor1, loadCycleOfSensor2, loadCycleOfSensor3
    global inputData, receivedData
    global loadingCycles
        
    while True:         
        inputData = bytearray()
        inputData.append(0x1)
        
        receivedData = receive_dataOnSocket(inputData) 
        if (receivedData != None): 
            unpacker_string = 3*'I I I I I B B B B'
            unpacker = struct.Struct('<B '+ unpacker_string)
            unpacked = unpacker.unpack(receivedData)
            
            # mutexSensVal.acquire() 
            loadCycleOfSensor1 = str(unpacked[1])
            loadCycleOfSensor2 = str(unpacked[12])
            loadCycleOfSensor3 = str(unpacked[20]) 
            # mutexSensVal.release()  

            cnt  = cnt + 1 		
            retString = loadCycleOfSensor1
            retString += ","
            retString += loadCycleOfSensor2
            retString += ","
            retString += loadCycleOfSensor3
            
            if (cnt == 1):
                cnt = 0 
                return retString
        else: 
            print("Received data on the socket is invalid")
            return loadCycleOfSensor1 + "," + loadCycleOfSensor2 + "," + loadCycleOfSensor3

def generate_random_values(): 
	global cnt
    
	while True: 		
		cnt  = cnt + 1
		# seed random number generator
		seed(1)
		# generate some random numbers
		val1 = round(random(), 3)
		val2 = round(random(), 3)
		val3 = round(random(), 3)
		if (cnt == 1):
			cnt = 0	 
			return str(val1) + ", " + str(val2) + ", " + str(val3)


def init_rtde(): 
    global rtdeLogger 
    global rtdeConn 
    global rtde_conn_ok, rtde_comm_started, recept_ok 

    rtdeLogger.debug("init_rtde")
    if not rtde_conn_ok :
        if rtdeConn is None:
            connect_rtde()
        if not recept_ok:
            init_conf()
            send_conf_to_robot()
        if not rtde_comm_started: 
            start_rtde_communication()

def connect_rtde(): 
    global rtdeLogger 
    global rtdeConn, controller_info  
    global rtde_conn_ok 

    rtdeLogger.debug("connect_rtde")
    try:
        rtdeConn = rtde.RTDE(urHost, urPort)
        rtdeConn.connect()
    except socket.timeout as e:
        rtdeLogger.error("failed to connect with robot", exc_info=True)
        controller_info = None
        rtdeConn = None 
        rtde_conn_ok = False 

    controller_info = rtdeConn.get_controller_version()
    rtdeLogger.info("connected with UR established")
    rtdeLogger.info(controller_info) 
    rtde_conn_ok = True 
    return rtde_conn_ok

def disconnect_rtde(): 
    global rtdeLogger 
    global rtdeConn, controller_info 
    global rtde_conn_ok, rtde_comm_started  

    rtdeConn.send_pause()
    rtdeConn.disconnect() 
    controller_info = None 
    rtde_conn_ok = False 
    rtde_comm_started = False 
    rtdeLogger.debug("disconnect_rtde")

def init_conf(): 
    global rtdeLogger 
    global dataAccessConfig 
    global state_names, state_types, set_freedrive_name, set_freedrive_type

    rtdeLogger.debug("init_conf")
    with dataAccessConfig:            
        conf = rtde_config.ConfigFile(config_filename)
        state_names, state_types = conf.get_recipe('state')
        set_freedrive_name, set_freedrive_type = conf.get_recipe('set_freedrive')

def send_conf_to_robot(): 
    global rtdeLogger 
    global rtdeConn 
    global recept_ok 
    global state_names, state_types, set_freedrive_name, set_freedrive_type, set_freedrive 

    rtdeLogger.debug("send_conf_to_robot")
    try: 
        if ((state_names != None) and (state_types != None) and (set_freedrive_name != None) and (set_freedrive_type != None)): 
            rtdeConn.send_output_setup(state_names, state_types)
            set_freedrive = rtdeConn.send_input_setup(set_freedrive_name, set_freedrive_type)

            set_freedrive.__dict__[b"input_bit_register_64"] = 0
            recept_ok = True
        else: 
            rtdeLogger.info("***state & set_freedrive*** recipes are invalid")
    except Exception as e:
        set_freedrive = None
        rtdeLogger.error("rtde recepts error", exc_info=True)
        recept_ok = False

def start_rtde_communication(): 
    global rtdeLogger 
    global rtdeConn 
    global rtde_comm_started  

    rtdeLogger.debug("start_communication")
    if not rtdeConn.is_connected():
        rtdeLogger.warning("no connection established")
        rtde_comm_started = False 
    if not rtdeConn.send_start():
        rtdeLogger.warning("starting data_sync failed") 
        rtde_comm_started = False 
        sys.exit()
    
    rtdeLogger.info("started communication")
    rtde_comm_started = True
    return rtde_comm_started

def rtde_freedrive(): 
    global rtdeLogger 
    global rtdeConn, set_freedrive 
    global keep_running 
    global mutexFreedriveMode 

    global loadCycleOfSensor1, loadCycleOfSensor2, loadCycleOfSensor3
    global c1, c2, c3

    lowerThr_loadCycleOfSensor1, upperThr_loadCycleOfSensor1 = 400, 700        
    lowerThr_loadCycleOfSensor2, upperThr_loadCycleOfSensor2 = 100, 400
    lowerThr_loadCycleOfSensor3, upperThr_loadCycleOfSensor3 = 200, 425

    lTh_c1 = lowerThr_loadCycleOfSensor1 
    uTh_c1 = upperThr_loadCycleOfSensor1 
    
    lTh_c2 = lowerThr_loadCycleOfSensor2 
    uTh_c2 = upperThr_loadCycleOfSensor2 
    
    lTh_c3 = lowerThr_loadCycleOfSensor3 
    uTh_c3 = upperThr_loadCycleOfSensor3 

    if (set_freedrive != None): 
        # Set the 'input_bit_register_64' to 0 by default
        set_freedrive.__dict__[b"input_bit_register_64"] = 0 
        
        if rtdeConn.is_connected(): 
            while not keep_running: 
                # receive the current state
                state = rtdeConn.receive()
                
                if state is None:
                    break
                
                # mutexSensVal.acquire()
                c1 = int (loadCycleOfSensor1) 
                rtdeLogger.info("Loading cycle of CapSen1 is " + str(c1)) 
                c2 = int (loadCycleOfSensor2) 
                rtdeLogger.info("Loading cycle of CapSen2 is " + str(c2)) 
                c3 = int (loadCycleOfSensor3) 
                rtdeLogger.info("Loading cycle of CapSen3 is " + str(c3)) 
                # mutexSensVal.release() 

                # mutexFreedriveMode.acquire()
                #  input_bit_register_64 refers to "general purpose input register 64" 
                # if "general purpose input register 64" variable is set to 1 => Freedrive mode is activated
                if ((lTh_c1 < c1 < uTh_c1) and (lTh_c2 < c2 < uTh_c2)):
                    set_freedrive.__dict__[b"input_bit_register_64"] = 1 
                    rtdeLogger.info("Capacitive sensors 1 and 2 are touched by the human operator, Freedrive mode activated")
                elif ((lTh_c2 < c2 < uTh_c2) and (lTh_c3 < c3 < uTh_c3)):
                    set_freedrive.__dict__[b"input_bit_register_64"] = 1 
                    rtdeLogger.info("Capacitive sensors 2 and 3 are touched by the human operator, Freedrive mode activated") 
                elif ((lTh_c3 < c3 < uTh_c3) and (lTh_c1 < c1 < uTh_c1)):
                    set_freedrive.__dict__[b"input_bit_register_64"] = 1 
                    rtdeLogger.info("Capacitive sensors 1 and 3 are touched by the human operator, Freedrive mode activated")
                else: 
                    set_freedrive.__dict__[b"input_bit_register_64"] = 0 
                    rtdeLogger.info("No two capacitive sensors are touched by the human operator, Freedrive mode deactivated")
                
                rtdeConn.send(set_freedrive) 
                # mutexFreedriveMode.release()
                sleep(0.1)
        else: 
            rtdeLogger.info("RTDE connection is disconnected")
    else: 
        rtdeLogger.info("***input recipe => set_freedrive*** is invalid")
    
    disconnect_rtde()

sys.stdout.write("CapSens daemon started")
sys.stderr.write("CapSens daemon started with caught exception")

# server = SimpleXMLRPCServer(("127.0.0.1", 40405))
server = SimpleXMLRPCServer(("127.0.0.1", 40405), allow_none=True)
# server = SimpleXMLRPCServer(("127.0.0.1", 40405), logRequests=True, allow_none=True)
server.register_introspection_functions()

server.register_function(set_title, "set_title")
server.register_function(get_title, "get_title")
server.register_function(get_message, "get_message")

server.register_function(set_ipaddr, "set_ipaddr")
server.register_function(get_ipaddr, "get_ipaddr")
server.register_function(set_portnum, "set_portnum")
server.register_function(get_portnum, "get_portnum")

server.register_function(set_led, "set_led")
server.register_function(set_rgb, "set_rgb")

server.register_instance(ConnectionState())
server.register_function(connect_socket, "connect_socket")

server.register_function(send_dataOnSocket, "send_dataOnSocket")
server.register_function(receive_dataOnSocket, "receive_dataOnSocket")

server.register_function(get_loadingCycleOfCapSen1, "get_loadingCycleOfCapSen1")
server.register_function(get_loadingCycleOfCapSen2, "get_loadingCycleOfCapSen2")
server.register_function(get_loadingCycleOfCapSen3, "get_loadingCycleOfCapSen3")
server.register_function(acq_loading_cycles, "acq_loading_cycles")

server.register_function(generate_random_values, "generate_random_values")

server.register_function(init_rtde, "init_rtde")
server.register_function(connect_rtde, "connect_rtde")
server.register_function(start_rtde_communication, "start_rtde_communication")
server.register_function(disconnect_rtde, "disconnect_rtde")
server.register_function(init_conf, "init_conf")
server.register_function(send_conf_to_robot, "send_conf_to_robot")
server.register_function(rtde_freedrive, "rtde_freedrive")
server.serve_forever()

Question => Is it even possible to use the RTDE interface via the remote procedure call method “XML-RPC”?

Problem => Polyscope GUI stops responding after the XML-RPC registered method ‘init_rtde’ is called from the InstallationNode of “CapSens” urcap.

The following are the technical details of “CapSens” urcap.

Virtual Machine => URCapsStarterPackage (https://plus.universal-robots.com/download-center/urcaps-starter-package/?f=1.9.0) which is included with URCaps SDK version 1.9.0

URCap API version utilized to develop “CapSens” urcap => 1.8.0

UR10 Universal Robot Control Software => URSoftware 3.12.0.90886 (Nov 15, 2019) & URSoftware 3.11.0.82155 (Aug 20, 2019)

Hi,
did you get any further feedback from your log entries?
Does your Python approach work if run from an external PC and not from a URCap?

Hello Stefan,

Thanks for your reply. I haven’t checked the log entries so far. Based on your suggestion, the log entries would be helpful to locate the cause.

Also, i will try to do the following if it helps me to debug the Python script while the URCap package is running in the URSim (UR Offline Simulator).

I will return a String message/value when a given thread has successfully started from the Python script. These String message/value will be returned to the JAVA side of the URCap. Then, i can view the returned String message/value in the console output of the URSim (UR Offline Simulator).

The python approach worked like a charm when the standalone client version of the Python script which was prepared without the XMLRPC server ran from an external PC.

Best Regards,
Sriniwas

1 Like

Hello Stefan,

Thanks for pointing me in the right direction as far as debugging the Python script in combination with a URCap is concerned. This really solved all my troubles.

Now, i am able to confirm myself the following things.

  • Individual class instances can be run as threads inside a given function which starts the threads. This particular function can be registered as a method to the XML-RPC server. When the Daemon starts, the function can be called from the URCap (in my case the function is called from the ‘XXXXXXXInstallationNodeContribution.java’ file of the URCap)

  • It is perfectly fine to use the RTDE interface via XML-RPC communication

Once again, thank for your time and consideration!

Best Regards,
Sriniwas

1 Like