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)