534 lines
16 KiB
Python
534 lines
16 KiB
Python
"""
|
|
Python library to control an UR robot through its TCP/IP interface
|
|
|
|
import urx
|
|
|
|
rob = urx.robot(192.168.0.100)
|
|
rob.set_tcp((x=01, z=0.232))
|
|
rob.movej((1,2,3,4,5,6), a, v)
|
|
rob.movel((x,y,z,a,b,c), a, v)
|
|
print "Current tool pose is: ", rob.getl()
|
|
rob.movelrel((0.1, 0, 0, 0, 0, 0), a, v)
|
|
rob.stopj(a)
|
|
|
|
robot.movel(x,y,z,a,b,c), wait=False)
|
|
while True :
|
|
sleep(0.1) #sleep first since the information may be outdated
|
|
if robot.isProgramRunning():
|
|
break
|
|
|
|
robot.movel(x,y,z,a,b,c), wait=False)
|
|
while.robot.getForce() < 50:
|
|
sleep(0.01)
|
|
robot.stopl()
|
|
|
|
try:
|
|
robot.movelrel(0,0,0.1,0,0,0)
|
|
except RobotError, ex:
|
|
print "Robot could not execute move (emergency stop for example), do somethhing", ex
|
|
|
|
Using matrices:
|
|
|
|
robot = Robot("192.168.1.1")
|
|
robot.set_tcp((0,0,0.23,0,0,0)
|
|
calib = mathd3d.Transform()
|
|
calib.orient.rotate_zb(pi/4) #just an example
|
|
robot.set_calibration_matrix(calib)
|
|
|
|
trans = robot.get_transform() # get current transformation matrix (tool to base)
|
|
trans.orient.rotate_yt(pi/2)
|
|
robot.apply_transform(trans)
|
|
trans.pos += math3d.Vector(0,0,0.3)
|
|
robot.apply_transform(trans)
|
|
|
|
|
|
#or only work with orientation part
|
|
o = robot.get_orientation()
|
|
o.rotate_yb(pi)
|
|
robot.orient(o)
|
|
|
|
TODO:
|
|
add more methods
|
|
movec
|
|
|
|
DOC LINK
|
|
http://support.universal-robots.com/URRobot/RemoteAccess
|
|
"""
|
|
from __future__ import absolute_import # necessary for import tricks to work with python2
|
|
|
|
__author__ = "Olivier Roulet-Dubonnet"
|
|
__copyright__ = "Copyright 2011-2012, Olivier Roulet-Dubonnet"
|
|
__credits__ = ["Olivier Roulet-Dubonnet"]
|
|
__license__ = "GPLv3"
|
|
__version__ = "0.3"
|
|
__status__ = "Development"
|
|
|
|
|
|
|
|
import time
|
|
import logging
|
|
|
|
|
|
MATH3D = True
|
|
try:
|
|
import math3d
|
|
except ImportError:
|
|
MATH3D = False
|
|
print("pymath3d library could not be found on this computer, disabling use of matrices")
|
|
|
|
from urx import urrtmon
|
|
from urx import ursecmon
|
|
from urx import tracker
|
|
|
|
|
|
class RobotException(Exception):
|
|
pass
|
|
|
|
|
|
class URRobot(object):
|
|
"""
|
|
Python interface to socket interface of UR robot.
|
|
programs are send to port 3002
|
|
data is read from secondary interface(10Hz?) and real-time interface(125Hz) (called Matlab interface in documentation)
|
|
Since parsing the RT interface uses som CPU, and does not support all robots versions, it is disabled by default
|
|
The RT interfaces is only used for the getForce related methods
|
|
Rmq: A program sent to the robot i executed immendiatly and any running program is stopped
|
|
"""
|
|
def __init__(self, host, useRTInterface=False):
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
if len(logging.root.handlers) == 0: #dirty hack
|
|
logging.basicConfig()
|
|
self.host = host
|
|
|
|
self.logger.info("Opening secondary monitor socket")
|
|
self.secmon = ursecmon.SecondaryMonitor(self.host) #data from robot at 10Hz
|
|
|
|
if useRTInterface:
|
|
self.logger.info("Opening real-time monitor socket")
|
|
self.rtmon = urrtmon.URRTMonitor(self.host)# som information is only available on rt interface
|
|
else:
|
|
self.rtmon = None
|
|
#the next 3 values must be conservative! otherwise we may wait forever
|
|
self.jointEpsilon = 0.05 # precision of joint movem used to wait for move completion
|
|
self.linearEpsilon = 0.0005 # precision of linear movement, used to wait for move completion
|
|
self.radialEpsilon = 0.05 # precision of radial movement, used to wait for move completion
|
|
if useRTInterface:
|
|
self.rtmon.start()
|
|
|
|
self.start_pose = [1.57, -1.77, 1.57, -1.8, -1.57, -1.57]
|
|
|
|
|
|
def __repr__(self):
|
|
return "Robot Object (IP=%s, state=%s)" % (self.host, self.secmon.getAllData()["RobotModeData"])
|
|
|
|
def __str__(self):
|
|
return self.__repr__()
|
|
|
|
def isRunning(self): # legacy
|
|
return self.secmon.running
|
|
|
|
def isProgramRunning(self):
|
|
return self.secmon.isProgramRunning()
|
|
|
|
def sendProgram(self, prog):
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def getTCPForce(self, wait=True):
|
|
"""
|
|
return measured force in TCP
|
|
if wait==True, waits for next packet before returning
|
|
"""
|
|
return self.rtmon.getTCFForce(wait)
|
|
|
|
def getForce(self, wait=True):
|
|
"""
|
|
length of force vector returned by getTCPForce
|
|
if wait==True, waits for next packet before returning
|
|
"""
|
|
tcpf = self.getTCPForce( wait)
|
|
force = 0
|
|
for i in tcpf:
|
|
force += i**2
|
|
return force**0.5
|
|
|
|
def moveToStartPose(self):
|
|
"""
|
|
move to pos defined in self.start_pose attribute
|
|
"""
|
|
self.movej(self.start_pose)
|
|
|
|
def setTcp(self, x=0, y=0, z=0, a=0, b=0, c=0):
|
|
"""
|
|
"""
|
|
if type(x) in (list, tuple):
|
|
if len(x) != 6:
|
|
raise Exception("Tcp is a 6 values list")
|
|
else:
|
|
arg = x
|
|
else:
|
|
arg = (x, y, z, a, b, c)
|
|
prog = "set_tcp(p[%s, %s, %s, %s, %s, %s])" % arg
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def setPayload(self, weight):
|
|
"""
|
|
set payload in Kg
|
|
"""
|
|
prog = "set_payload(%s)" % weight
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def setGravity(self, vector):
|
|
"""
|
|
set direction of gravity
|
|
"""
|
|
prog = "set_gravity(%s)" % list(vector)
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def sendMessage(self, msg):
|
|
"""
|
|
send message to the GUI log tab on the robot controller
|
|
"""
|
|
prog = "textmsg(%s)" % msg
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def setDigitalOut(self, output, val):
|
|
"""
|
|
set digital output. val is a bool
|
|
"""
|
|
if val in (True, 1):
|
|
val = "True"
|
|
else:
|
|
val = "False"
|
|
self.secmon.sendProgram('digital_out[%s]=%s' % (output, val))
|
|
|
|
def getAnalogInputs(self):
|
|
"""
|
|
get analog input
|
|
"""
|
|
data = self.secmon.getAllData()
|
|
return data["MasterBoardData"]["analogInput0"], data["MasterBoardData"]["analogInput1"]
|
|
|
|
def getDigitalOutputBits(self):
|
|
"""
|
|
get digital output
|
|
"""
|
|
data = self.secmon.getAllData()
|
|
return data["MasterBoardData"]["digitalOutputBits"]
|
|
|
|
def getDigitalOutput(self, val):
|
|
"""
|
|
get digital output
|
|
"""
|
|
data = self.secmon.getAllData()
|
|
output = data["MasterBoardData"]["digitalOutputBits"]
|
|
mask = 1 << val
|
|
if (output & mask):
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
|
|
|
|
|
|
def setAnalogOut(self, output, val):
|
|
"""
|
|
set analog output, val is a float
|
|
"""
|
|
prog = "set_analog_output(%s, %s)" % (output, val)
|
|
self.secmon.sendProgram(prog)
|
|
|
|
|
|
def setToolVoltage(self, val):
|
|
"""
|
|
set voltage to be delivered to the tool, val is 0, 12 or 24
|
|
"""
|
|
prog = "set_tool_voltage(%s)" % (val)
|
|
self.secmon.sendProgram(prog)
|
|
|
|
def movejrel(self, joints, acc=0.1, vel=0.05, wait=True):
|
|
"""
|
|
relative joint move
|
|
"""
|
|
l = self.getj()
|
|
newl = [v + l[i] for i, v in enumerate(joints)]
|
|
return self.movej(newl, acc, vel, wait )
|
|
|
|
def movej(self, joints, acc=0.1, vel=0.05, wait=True):
|
|
"""
|
|
wrapper around the movej command in URRobot
|
|
"""
|
|
#todo: should check joints input and velocity
|
|
prog = "movej(%s, a=%s, v=%s)" % (list(joints), acc, vel)
|
|
self.sendProgram(prog)
|
|
if not wait:
|
|
return None
|
|
else:
|
|
while True:
|
|
time.sleep(0.05)# it is important to sleep since robot may takes a while to get into running state
|
|
if not self.isRunning():#FIXME add more tests
|
|
raise RobotException("Robot stopped")
|
|
currentjoints = self.getj()
|
|
if self._eq(currentjoints, joints) and not self.secmon.isProgramRunning():
|
|
return currentjoints
|
|
|
|
def getj(self, wait=False):
|
|
"""
|
|
get joints position
|
|
"""
|
|
jts = self.secmon.getJointData(wait)
|
|
return [jts["q_actual0"], jts["q_actual1"], jts["q_actual2"], jts["q_actual3"], jts["q_actual4"], jts["q_actual5"]]
|
|
|
|
|
|
def movelrel(self, tpose, acc=0.01, vel=0.01, wait=True):
|
|
"""
|
|
relative move in cartesian coordinate
|
|
"""
|
|
l = self.getl()
|
|
newl = [v + l[i] for i, v in enumerate(tpose)]
|
|
return self.movel(newl, acc, vel, wait )
|
|
|
|
def movel(self, tpose, acc=0.01, vel=0.01, wait=True):
|
|
"""
|
|
move in tool corrdinate system
|
|
wrapper around the movel command in URRobot
|
|
"""
|
|
#todo: should check joints input and velocity
|
|
tpose = [round(i, 2) for i in tpose]
|
|
prog = "movel(p%s, a=%s, v=%s)" % (tpose, acc, vel)
|
|
self.sendProgram(prog)
|
|
if not wait:
|
|
return None
|
|
else:
|
|
time.sleep(0.05)# it is important to sleep since robot may takes a while to get into running state
|
|
while True:
|
|
if not self.isRunning():#FIXME add more tests
|
|
raise RobotException("Robot stopped")
|
|
pose = self.getl(wait=True)
|
|
if self._eqpose(pose, tpose) and not self.secmon.isProgramRunning():
|
|
return pose
|
|
|
|
def getl(self, wait=False):
|
|
"""
|
|
get TCP position
|
|
"""
|
|
pose = self.secmon.getCartesianInfo(wait)
|
|
if pose:
|
|
pose = [pose["X"], pose["Y"], pose["Z"], pose["Rx"], pose["Ry"], pose["Rz"]]
|
|
self.logger.debug("Current pose from robot: " + str(pose))
|
|
return pose
|
|
|
|
def movels(self, joints, acc, vel , radius, wait=True):
|
|
"""
|
|
where joints is a list of list. dvs: several movel commands must be send as one program in order for radius blending to work.
|
|
This is could easily be implemented in movel by detecting type of the joint variable
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def stopl(self, acc = 0.5):
|
|
self.sendProgram("stopl(%s)" % acc)
|
|
|
|
def stopj(self, acc = 1.5):
|
|
self.sendProgram("stopj(%s)" % acc)
|
|
|
|
def stop(self):
|
|
self.stopj()
|
|
|
|
def _eq(self, l1, l2):
|
|
"""
|
|
robot joints precision is 0.01, donot give anything smaller!!!
|
|
"""
|
|
for i in range(0, len(l1)):
|
|
if abs(l1[i] -l2[i]) > self.jointEpsilon:
|
|
return False
|
|
return True
|
|
|
|
def _eqpose(self, l1, l2):
|
|
"""
|
|
epsilonl is for x,y,z
|
|
epsilonr is for a,b,c
|
|
robot joints precision is 0.01, do not give anything smaller!!!
|
|
"""
|
|
for i in range(0, 3):
|
|
if abs(l1[i] - l2[i]) > self.linearEpsilon:
|
|
#print "param: ", i, "val: ", l1[i], "-", l2[i] , "=", abs(l1[i] -l2[i]), " is not under ", self.linearEpsilon
|
|
return False
|
|
for i in range(3, 6):
|
|
if abs(l1[i] - l2[i]) > self.radialEpsilon:
|
|
#print "param: ", i, "val: ", l1[i], "-", l2[i] , "=", abs(l1[i] -l2[i]), " is not under ", self.radialEpsilon
|
|
return False
|
|
return True
|
|
|
|
def cleanup(self):
|
|
self.logger.info("Closing sockets to robot")
|
|
self.secmon.cleanup()
|
|
if self.rtmon:
|
|
self.rtmon.stop()
|
|
|
|
|
|
|
|
class Robot(object):
|
|
"""
|
|
Generic Python interface to an industrial robot.
|
|
Compare to the URRobot class, this class adds the possibilty to work directly with matrices
|
|
and includes support for calibrating the robot coordinate system
|
|
and style portet to PEP 8
|
|
"""
|
|
def __init__(self, host, useRTInterface=False):
|
|
self.robot = URRobot(host, useRTInterface)
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
if len(logging.root.handlers) == 0: #dirty hack
|
|
logging.basicConfig()
|
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
self.default_linear_acceleration = 0.01
|
|
self.default_linear_velocity = 0.01
|
|
|
|
self.calibration = math3d.Transform() #identity
|
|
self.inverse = self.calibration.inverse()
|
|
self.tracker = None
|
|
|
|
def set_tcp(self, tcp):
|
|
if type(tcp) == math3d.Transform:
|
|
tcp = tcp.pose_vector
|
|
self.robot.setTcp(tcp)
|
|
|
|
def set_calibration_matrix(self, matrix):
|
|
self.calibration = matrix
|
|
self.inverse = self.calibration.inverse()
|
|
|
|
def orient(self, orient, acc=None, vel=None, wait=True):
|
|
if type(orient) != math3d.Orientation:
|
|
orient = math3d.Orientation(orient)
|
|
trans = self.get_transform()
|
|
trans.orient = orient
|
|
self.apply_transform(trans, acc, vel, wait)
|
|
|
|
def set_orientation(self, orient, acc=None, vel=None, wait=True):
|
|
self.orient(orient, acc, vel, wait)
|
|
|
|
def translate(self, vect, acc=None, vel=None, wait=True):
|
|
trans = self.get_transform()
|
|
trans.pos += math3d.Vector(vect)
|
|
return self.apply_transform(trans, acc, vel, wait)
|
|
|
|
def set_pos(self, vect, acc=None, vel=None, wait=True):
|
|
trans = math3d.Transform(self.get_orientation(), math3d.Vector(vect))
|
|
return self.apply_transform(trans, acc, vel, wait)
|
|
|
|
def stop(self):
|
|
self.robot.stop()
|
|
|
|
def apply_transform(self, trans, acc=None, vel=None, wait=True):
|
|
if not acc:
|
|
acc = self.default_linear_acceleration
|
|
if not vel:
|
|
vel = self.default_linear_velocity
|
|
t = self.calibration * trans
|
|
pose = self.robot.movel(t.pose_vector, acc, vel, wait)
|
|
if pose: #movel does not return anything when wait is False
|
|
return self.inverse * math3d.Transform(pose)
|
|
|
|
def add_transform(self, trans, acc=None, vel=None, wait=True):
|
|
pose = self.get_transform()
|
|
return self.apply_transform(trans * pose, acc, vel, wait)
|
|
|
|
def get_transform(self, wait=False):
|
|
pose = self.robot.getl(wait)
|
|
trans = self.inverse * math3d.Transform(pose)
|
|
return trans
|
|
|
|
def get_orientation(self, wait=False):
|
|
trans = self.get_transform(wait)
|
|
return trans.orient
|
|
|
|
def get_pos(self, wait=False):
|
|
trans = self.get_transform(wait)
|
|
return trans.pos
|
|
|
|
def movel(self, pose, acc=None, vel=None, wait=True):
|
|
t = math3d.Transform(pose)
|
|
self.apply_transform(t, acc, vel, wait)
|
|
|
|
def movelrel(self, pose, acc=None, vel=None, wait=True):
|
|
self.add_transform(math3d.Transform(pose), acc, vel, wait)
|
|
|
|
def movel_cnc(self, pose, acc=None, vel=None, wait=True):
|
|
"""
|
|
One could implement a CNC like interface with ABC like euler angles
|
|
Not implemented yet
|
|
"""
|
|
pass
|
|
|
|
def getl(self, wait=False):
|
|
t = self.get_transform(wait)
|
|
return t.pose_vector
|
|
|
|
def is_running(self):
|
|
return self.robot.isRunning()
|
|
|
|
def is_program_running(self):
|
|
return self.robot.isProgramRunning()
|
|
|
|
def set_payload(self, weight):
|
|
return self.robot.setPayload(weight)
|
|
|
|
def set_gravity(self, vector):
|
|
if type(vector) == math3d.Vector:
|
|
vector = vector.list
|
|
return self.robot.setGravity(vector)
|
|
|
|
def set_digital_out(self, output, val):
|
|
self.robot.setDigitalOut(output, val)
|
|
|
|
def movej(self, joints, acc=0.1, vel=0.05, wait=True):
|
|
"""
|
|
wrapper around the movej command in URRobot
|
|
"""
|
|
self.robot.movej(joints, acc, vel, wait)
|
|
|
|
def getj(self, wait=False):
|
|
return self.robot.getj(wait)
|
|
|
|
def movejrel(self, joints, acc=0.1, vel=0.05, wait=True):
|
|
"""
|
|
relative joint move
|
|
"""
|
|
self.robot.movejrel(joints, acc, vel, wait)
|
|
|
|
def cleanup(self):
|
|
self.robot.cleanup()
|
|
|
|
def get_tracker(self):
|
|
"""
|
|
return an object able to track robot move for logging
|
|
"""
|
|
t = tracker.Tracker(self.robot.host)
|
|
t.set_calibration_matrix(self.calibration)
|
|
return t
|
|
|
|
|
|
if not MATH3D:
|
|
Robot = URRobot
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
#robot = Robot( '192.168.1.6')
|
|
robot = Robot( '192.168.1.5')
|
|
r = robot
|
|
|
|
#robot = Robot( '192.168.1.2', useRTInterface=False)
|
|
#robot = Robot( '192.168.1.216')
|
|
#robot = Robot( '192.168.1.216')
|
|
#p = r.sendProgram(open("examples/example.prog").read(), wait=False)
|
|
|
|
from IPython.frontend.terminal.embed import InteractiveShellEmbed
|
|
ipshell = InteractiveShellEmbed( banner1="\n\n robot object is available \n\n")
|
|
ipshell(local_ns=locals())
|
|
|
|
finally:
|
|
if "robot" in dir():
|
|
robot.cleanup()
|
|
|
|
|
|
|
|
|