00001
00002 import roslib; roslib.load_manifest('ur5_driver')
00003 import time, sys, threading, math
00004 import copy
00005 import datetime
00006 import socket, select
00007 import struct
00008 import traceback, code
00009 import optparse
00010 import SocketServer
00011 from BeautifulSoup import BeautifulSoup
00012
00013 import rospy
00014 import actionlib
00015 from sensor_msgs.msg import JointState
00016 from control_msgs.msg import FollowJointTrajectoryAction
00017 from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
00018
00019 from deserialize import RobotState, RobotMode
00020
00021 prevent_programming = False
00022
00023
00024
00025
00026
00027
00028 joint_offsets = {}
00029
00030 PORT=30002
00031 REVERSE_PORT = 50001
00032
00033 MSG_OUT = 1
00034 MSG_QUIT = 2
00035 MSG_JOINT_STATES = 3
00036 MSG_MOVEJ = 4
00037 MSG_WAYPOINT_FINISHED = 5
00038 MSG_STOPJ = 6
00039 MSG_SERVOJ = 7
00040 MULT_jointstate = 10000.0
00041 MULT_time = 1000000.0
00042 MULT_blend = 1000.0
00043
00044 JOINT_NAMES = ['shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint',
00045 'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint']
00046
00047 Q1 = [2.2,0,-1.57,0,0,0]
00048 Q2 = [1.5,0,-1.57,0,0,0]
00049 Q3 = [1.5,-0.2,-1.57,0,0,0]
00050
00051
00052 connected_robot = None
00053 connected_robot_lock = threading.Lock()
00054 connected_robot_cond = threading.Condition(connected_robot_lock)
00055 pub_joint_states = rospy.Publisher('joint_states', JointState)
00056
00057
00058 class EOF(Exception): pass
00059
00060 def dumpstacks():
00061 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
00062 code = []
00063 for threadId, stack in sys._current_frames().items():
00064 code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
00065 for filename, lineno, name, line in traceback.extract_stack(stack):
00066 code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
00067 if line:
00068 code.append(" %s" % (line.strip()))
00069 print "\n".join(code)
00070
00071 def log(s):
00072 print "[%s] %s" % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'), s)
00073
00074
00075 RESET_PROGRAM = '''def resetProg():
00076 sleep(0.0)
00077 end
00078 '''
00079
00080
00081 class UR5Connection(object):
00082 TIMEOUT = 1.0
00083
00084 DISCONNECTED = 0
00085 CONNECTED = 1
00086 READY_TO_PROGRAM = 2
00087 EXECUTING = 3
00088
00089 def __init__(self, hostname, port, program):
00090 self.__thread = None
00091 self.__sock = None
00092 self.robot_state = self.DISCONNECTED
00093 self.hostname = hostname
00094 self.port = port
00095 self.program = program
00096 self.last_state = None
00097
00098 def connect(self):
00099 if self.__sock:
00100 self.disconnect()
00101 self.__buf = ""
00102 self.robot_state = self.CONNECTED
00103 self.__sock = socket.create_connection((self.hostname, self.port))
00104 self.__keep_running = True
00105 self.__thread = threading.Thread(name="UR5Connection", target=self.__run)
00106 self.__thread.daemon = True
00107 self.__thread.start()
00108
00109 def send_program(self):
00110 global prevent_programming
00111 if prevent_programming:
00112 rospy.loginfo("Programming is currently prevented")
00113 return
00114 assert self.robot_state in [self.READY_TO_PROGRAM, self.EXECUTING]
00115 rospy.loginfo("Programming the robot at %s" % self.hostname)
00116 self.__sock.sendall(self.program)
00117 self.robot_state = self.EXECUTING
00118
00119 def send_reset_program(self):
00120 self.__sock.sendall(RESET_PROGRAM)
00121 self.robot_state = self.READY_TO_PROGRAM
00122
00123 def disconnect(self):
00124 if self.__thread:
00125 self.__keep_running = False
00126 self.__thread.join()
00127 self.__thread = None
00128 if self.__sock:
00129 self.__sock.close()
00130 self.__sock = None
00131 self.last_state = None
00132 self.robot_state = self.DISCONNECTED
00133
00134 def ready_to_program(self):
00135 return self.robot_state in [self.READY_TO_PROGRAM, self.EXECUTING]
00136
00137 def __trigger_disconnected(self):
00138 log("Robot disconnected")
00139 self.robot_state = self.DISCONNECTED
00140 def __trigger_ready_to_program(self):
00141 rospy.loginfo("Robot ready to program")
00142 def __trigger_halted(self):
00143 log("Halted")
00144
00145 def __on_packet(self, buf):
00146 state = RobotState.unpack(buf)
00147 self.last_state = state
00148
00149
00150
00151
00152 if not state.robot_mode_data.real_robot_enabled:
00153 rospy.logfatal("Real robot is no longer enabled. Driver is fuxored")
00154 time.sleep(2)
00155 sys.exit(1)
00156
00157
00158
00159
00160 if self.robot_state != self.EXECUTING:
00161 msg = JointState()
00162 msg.header.stamp = rospy.get_rostime()
00163 msg.header.frame_id = "From binary state data"
00164 msg.name = joint_names
00165 msg.position = [0.0] * 6
00166 for i, jd in enumerate(state.joint_data):
00167 msg.position[i] = jd.q_actual + joint_offsets.get(joint_names[i], 0.0)
00168 msg.velocity = [jd.qd_actual for jd in state.joint_data]
00169 msg.effort = [0]*6
00170 pub_joint_states.publish(msg)
00171 self.last_joint_states = msg
00172
00173
00174 can_execute = (state.robot_mode_data.robot_mode in [RobotMode.READY, RobotMode.RUNNING])
00175 if self.robot_state == self.CONNECTED:
00176 if can_execute:
00177 self.__trigger_ready_to_program()
00178 self.robot_state = self.READY_TO_PROGRAM
00179 elif self.robot_state == self.READY_TO_PROGRAM:
00180 if not can_execute:
00181 self.robot_state = self.CONNECTED
00182 elif self.robot_state == self.EXECUTING:
00183 if not can_execute:
00184 self.__trigger_halted()
00185 self.robot_state = self.CONNECTED
00186
00187 def __run(self):
00188 while self.__keep_running:
00189 r, _, _ = select.select([self.__sock], [], [], self.TIMEOUT)
00190 if r:
00191 more = self.__sock.recv(4096)
00192 if more:
00193 self.__buf = self.__buf + more
00194
00195
00196 packet_length, ptype = struct.unpack_from("!IB", self.__buf)
00197 if len(self.__buf) >= packet_length:
00198 packet, self.__buf = self.__buf[:packet_length], self.__buf[packet_length:]
00199 self.__on_packet(packet)
00200 else:
00201 self.__trigger_disconnected()
00202 self.__keep_running = False
00203
00204 else:
00205 self.__trigger_disconnected()
00206 self.__keep_running = False
00207
00208
00209 def setConnectedRobot(r):
00210 global connected_robot, connected_robot_lock
00211 with connected_robot_lock:
00212 connected_robot = r
00213 connected_robot_cond.notify()
00214
00215 def getConnectedRobot(wait=False, timeout=-1):
00216 started = time.time()
00217 with connected_robot_lock:
00218 if wait:
00219 while not connected_robot:
00220 if timeout >= 0 and time.time() > started + timeout:
00221 break
00222 connected_robot_cond.wait(0.2)
00223 return connected_robot
00224
00225
00226 class CommanderTCPHandler(SocketServer.BaseRequestHandler):
00227
00228 def recv_more(self):
00229 while True:
00230 r, _, _ = select.select([self.request], [], [], 0.2)
00231 if r:
00232 more = self.request.recv(4096)
00233 if not more:
00234 raise EOF("EOF on recv")
00235 return more
00236 else:
00237 now = rospy.get_rostime()
00238 if self.last_joint_states and \
00239 self.last_joint_states.header.stamp < now - rospy.Duration(1.0):
00240 rospy.logerr("Stopped hearing from robot (last heard %.3f sec ago). Disconnected" % \
00241 (now - self.last_joint_states.header.stamp).to_sec())
00242 raise EOF()
00243
00244 def handle(self):
00245 self.socket_lock = threading.Lock()
00246 self.last_joint_states = None
00247 setConnectedRobot(self)
00248 print "Handling a request"
00249 try:
00250 buf = self.recv_more()
00251 if not buf: return
00252
00253 while True:
00254
00255
00256
00257 mtype = struct.unpack_from("!i", buf, 0)[0]
00258 buf = buf[4:]
00259
00260
00261 if mtype == MSG_OUT:
00262
00263 i = buf.find("~")
00264 while i < 0:
00265 buf = buf + self.recv_more()
00266 i = buf.find("~")
00267 if len(buf) > 2000:
00268 raise Exception("Probably forgot to terminate a string: %s..." % buf[:150])
00269 s, buf = buf[:i], buf[i+1:]
00270 log("Out: %s" % s)
00271
00272 elif mtype == MSG_JOINT_STATES:
00273 while len(buf) < 3*(6*4):
00274 buf = buf + self.recv_more()
00275 state_mult = struct.unpack_from("!%ii" % (3*6), buf, 0)
00276 buf = buf[3*6*4:]
00277 state = [s / MULT_jointstate for s in state_mult]
00278
00279 msg = JointState()
00280 msg.header.stamp = rospy.get_rostime()
00281 msg.name = joint_names
00282 msg.position = [0.0] * 6
00283 for i, q_meas in enumerate(state[:6]):
00284 msg.position[i] = q_meas + joint_offsets.get(joint_names[i], 0.0)
00285 msg.velocity = state[6:12]
00286 msg.effort = state[12:18]
00287 self.last_joint_states = msg
00288 pub_joint_states.publish(msg)
00289 elif mtype == MSG_QUIT:
00290 print "Quitting"
00291 raise EOF("Received quit")
00292 elif mtype == MSG_WAYPOINT_FINISHED:
00293 while len(buf) < 4:
00294 buf = buf + self.recv_more()
00295 waypoint_id = struct.unpack_from("!i", buf, 0)[0]
00296 buf = buf[4:]
00297 print "Waypoint finished (not handled)"
00298 else:
00299 raise Exception("Unknown message type: %i" % mtype)
00300
00301 if not buf:
00302 buf = buf + self.recv_more()
00303 except EOF, ex:
00304 print "Connection closed (command):", ex
00305 setConnectedRobot(None)
00306
00307 def send_quit(self):
00308 with self.socket_lock:
00309 self.request.send(struct.pack("!i", MSG_QUIT))
00310
00311 def send_servoj(self, waypoint_id, q_actual, t):
00312 assert(len(q_actual) == 6)
00313 q_robot = [0.0] * 6
00314 for i, q in enumerate(q_actual):
00315 q_robot[i] = q - joint_offsets.get(joint_names[i], 0.0)
00316 params = [MSG_SERVOJ, waypoint_id] + \
00317 [MULT_jointstate * qq for qq in q_robot] + \
00318 [MULT_time * t]
00319 buf = struct.pack("!%ii" % len(params), *params)
00320 with self.socket_lock:
00321 self.request.send(buf)
00322
00323
00324 def send_stopj(self):
00325 with self.socket_lock:
00326 self.request.send(struct.pack("!i", MSG_STOPJ))
00327
00328 def set_waypoint_finished_cb(self, cb):
00329 self.waypoint_finished_cb = cb
00330
00331
00332 def get_joint_states(self):
00333 return self.last_joint_states
00334
00335
00336 class TCPServer(SocketServer.TCPServer):
00337 allow_reuse_address = True
00338 timeout = 5
00339
00340
00341
00342 def joinAll(threads):
00343 while any(t.isAlive() for t in threads):
00344 for t in threads:
00345 t.join(0.2)
00346
00347
00348
00349 def get_segment_duration(traj, index):
00350 if index == 0:
00351 return traj.points[0].time_from_start.to_sec()
00352 return (traj.points[index].time_from_start - traj.points[index-1].time_from_start).to_sec()
00353
00354
00355
00356 def reorder_traj_joints(traj, joint_names):
00357 order = [traj.joint_names.index(j) for j in joint_names]
00358
00359 new_points = []
00360 for p in traj.points:
00361 new_points.append(JointTrajectoryPoint(
00362 positions = [p.positions[i] for i in order],
00363 velocities = [p.velocities[i] for i in order] if p.velocities else [],
00364 accelerations = [p.accelerations[i] for i in order] if p.accelerations else [],
00365 time_from_start = p.time_from_start))
00366 traj.joint_names = joint_names
00367 traj.points = new_points
00368
00369 def interp_cubic(p0, p1, t_abs):
00370 T = (p1.time_from_start - p0.time_from_start).to_sec()
00371 t = t_abs - p0.time_from_start.to_sec()
00372 q = [0] * 6
00373 qdot = [0] * 6
00374 qddot = [0] * 6
00375 for i in range(len(p0.positions)):
00376 a = p0.positions[i]
00377 b = p0.velocities[i]
00378 c = (-3*p0.positions[i] + 3*p1.positions[i] - 2*T*p0.velocities[i] - T*p1.velocities[i]) / T**2
00379 d = (2*p0.positions[i] - 2*p1.positions[i] + T*p0.velocities[i] + T*p1.velocities[i]) / T**3
00380
00381 q[i] = a + b*t + c*t**2 + d*t**3
00382 qdot[i] = b + 2*c*t + 3*d*t**2
00383 qddot[i] = 2*c + 6*d*t
00384 return JointTrajectoryPoint(q, qdot, qddot, rospy.Duration(t_abs))
00385
00386
00387
00388 def sample_traj(traj, t):
00389
00390 if t <= 0.0:
00391 return copy.deepcopy(traj.points[0])
00392
00393 if t >= traj.points[-1].time_from_start.to_sec():
00394 return copy.deepcopy(traj.points[-1])
00395
00396
00397 i = 0
00398 while traj.points[i+1].time_from_start.to_sec() < t:
00399 i += 1
00400 return interp_cubic(traj.points[i], traj.points[i+1], t)
00401
00402 def traj_is_finite(traj):
00403 for pt in traj.points:
00404 for p in pt.positions:
00405 if math.isinf(p) or math.isnan(p):
00406 return False
00407 for v in pt.velocities:
00408 if math.isinf(v) or math.isnan(v):
00409 return False
00410 return True
00411
00412 def has_limited_velocities(traj):
00413 for p in traj.points:
00414 for v in p.velocities:
00415 if math.fabs(v) > max_velocity:
00416 return False
00417 return True
00418
00419 def has_velocities(traj):
00420 for p in traj.points:
00421 if len(p.velocities) != len(p.positions):
00422 return False
00423 return True
00424
00425 def within_tolerance(a_vec, b_vec, tol_vec):
00426 for a, b, tol in zip(a_vec, b_vec, tol_vec):
00427 if abs(a - b) > tol:
00428 return False
00429 return True
00430
00431 class UR5TrajectoryFollower(object):
00432 RATE = 0.02
00433 def __init__(self, robot, goal_time_tolerance=None):
00434 self.goal_time_tolerance = goal_time_tolerance or rospy.Duration(0.0)
00435 self.joint_goal_tolerances = [0.05, 0.05, 0.05, 0.05, 0.05, 0.05]
00436 self.following_lock = threading.Lock()
00437 self.T0 = time.time()
00438 self.robot = robot
00439 self.server = actionlib.ActionServer("follow_joint_trajectory",
00440 FollowJointTrajectoryAction,
00441 self.on_goal, self.on_cancel, auto_start=False)
00442
00443 self.goal_handle = None
00444 self.traj = None
00445 self.traj_t0 = 0.0
00446 self.first_waypoint_id = 10
00447 self.tracking_i = 0
00448 self.pending_i = 0
00449
00450 self.update_timer = rospy.Timer(rospy.Duration(self.RATE), self._update)
00451
00452 def set_robot(self, robot):
00453
00454 if self.goal_handle:
00455 self.goal_handle.set_canceled()
00456 self.goal_handle = None
00457 self.traj = None
00458 self.robot = robot
00459 if self.robot:
00460 self.init_traj_from_robot()
00461
00462
00463
00464 def init_traj_from_robot(self):
00465 if not self.robot: raise Exception("No robot connected")
00466
00467 state = self.robot.get_joint_states()
00468 while not state:
00469 time.sleep(0.1)
00470 state = self.robot.get_joint_states()
00471 self.traj_t0 = time.time()
00472 self.traj = JointTrajectory()
00473 self.traj.joint_names = joint_names
00474 self.traj.points = [JointTrajectoryPoint(
00475 positions = state.position,
00476 velocities = [0] * 6,
00477 accelerations = [0] * 6,
00478 time_from_start = rospy.Duration(0.0))]
00479
00480 def start(self):
00481 self.init_traj_from_robot()
00482 self.server.start()
00483 print "The action server for this driver has been started"
00484
00485 def on_goal(self, goal_handle):
00486 log("on_goal")
00487
00488
00489 if not self.robot:
00490 rospy.logerr("Received a goal, but the robot is not connected")
00491 goal_handle.set_rejected()
00492 return
00493
00494
00495 if set(goal_handle.get_goal().trajectory.joint_names) != set(joint_names):
00496 rospy.logerr("Received a goal with incorrect joint names: (%s)" % \
00497 ', '.join(goal_handle.get_goal().trajectory.joint_names))
00498 goal_handle.set_rejected()
00499 return
00500
00501 if not traj_is_finite(goal_handle.get_goal().trajectory):
00502 rospy.logerr("Received a goal with infinites or NaNs")
00503 goal_handle.set_rejected(text="Received a goal with infinites or NaNs")
00504 return
00505
00506
00507 if not has_velocities(goal_handle.get_goal().trajectory):
00508 rospy.logerr("Received a goal without velocities")
00509 goal_handle.set_rejected(text="Received a goal without velocities")
00510 return
00511
00512
00513 if not has_limited_velocities(goal_handle.get_goal().trajectory):
00514 message = "Received a goal with velocities that are higher than %f" % max_velocity
00515 rospy.logerr(message)
00516 goal_handle.set_rejected(text=message)
00517 return
00518
00519
00520 reorder_traj_joints(goal_handle.get_goal().trajectory, joint_names)
00521
00522 with self.following_lock:
00523 if self.goal_handle:
00524
00525 self.goal_handle.set_canceled()
00526 self.first_waypoint_id += len(self.goal_handle.get_goal().trajectory.points)
00527 self.goal_handle = None
00528
00529
00530 now = time.time()
00531 point0 = sample_traj(self.traj, now)
00532 point0.time_from_start = rospy.Duration(0.0)
00533 goal_handle.get_goal().trajectory.points.insert(0, point0)
00534 self.traj_t0 = now
00535
00536
00537 self.goal_handle = goal_handle
00538 self.traj = goal_handle.get_goal().trajectory
00539 self.goal_handle.set_accepted()
00540
00541 def on_cancel(self, goal_handle):
00542 log("on_cancel")
00543 if goal_handle == self.goal_handle:
00544 with self.following_lock:
00545
00546 STOP_DURATION = 0.5
00547 now = time.time()
00548 point0 = sample_traj(self.traj, now - self.traj_t0)
00549 point0.time_from_start = rospy.Duration(0.0)
00550 point1 = sample_traj(self.traj, now - self.traj_t0 + STOP_DURATION)
00551 point1.velocities = [0] * 6
00552 point1.accelerations = [0] * 6
00553 point1.time_from_start = rospy.Duration(STOP_DURATION)
00554 self.traj_t0 = now
00555 self.traj = JointTrajectory()
00556 self.traj.joint_names = joint_names
00557 self.traj.points = [point0, point1]
00558
00559 self.goal_handle.set_canceled()
00560 self.goal_handle = None
00561 else:
00562 goal_handle.set_canceled()
00563
00564 last_now = time.time()
00565 def _update(self, event):
00566 if self.robot and self.traj:
00567 now = time.time()
00568 if (now - self.traj_t0) <= self.traj.points[-1].time_from_start.to_sec():
00569 setpoint = sample_traj(self.traj, now - self.traj_t0)
00570 try:
00571 self.robot.send_servoj(999, setpoint.positions, 4 * self.RATE)
00572 except socket.error:
00573 pass
00574 else:
00575 if self.goal_handle:
00576 last_point = self.traj.points[-1]
00577 state = self.robot.get_joint_states()
00578 position_in_tol = within_tolerance(state.position, last_point.positions, self.joint_goal_tolerances)
00579 velocity_in_tol = within_tolerance(state.velocity, last_point.velocities, [0.005]*6)
00580 if position_in_tol and velocity_in_tol:
00581
00582 self.goal_handle.set_succeeded()
00583 self.goal_handle = None
00584 elif now - (self.traj_t0 + last_point.time_from_start.to_sec()) > self.goal_time_tolerance.to_sec():
00585
00586 rospy.logwarn("Took too long to reach the goal.\nDesired: %s\nactual: %s\nvelocity: %s" % \
00587 (last_point.positions, state.position, state.velocity))
00588 self.goal_handle.set_aborted(text="Took too long to reach the goal")
00589 self.goal_handle = None
00590
00591
00592
00593
00594 def load_joint_offsets(joint_names):
00595 robot_description = rospy.get_param("robot_description")
00596 soup = BeautifulSoup(robot_description)
00597
00598 result = {}
00599 for joint in joint_names:
00600 try:
00601 joint_elt = soup.find('joint', attrs={'name': joint})
00602 calibration_offset = float(joint_elt.calibration_offset["value"])
00603 result[joint] = calibration_offset
00604 except Exception, ex:
00605 rospy.logwarn("No calibration offset for joint \"%s\"" % joint)
00606 return result
00607
00608 def main():
00609 rospy.init_node('ur5_driver', disable_signals=True)
00610 if rospy.get_param("use_sim_time", False):
00611 rospy.logfatal("use_sim_time is set!!!")
00612 sys.exit(1)
00613 global prevent_programming
00614 prevent_programming = rospy.get_param("prevent_programming", False)
00615 prefix = rospy.get_param("~prefix", "")
00616 print "Setting prefix to %s" % prefix
00617 global joint_names
00618 joint_names = [prefix + name for name in JOINT_NAMES]
00619
00620
00621 parser = optparse.OptionParser(usage="usage: %prog robot_hostname")
00622 (options, args) = parser.parse_args(rospy.myargv()[1:])
00623 if len(args) != 1:
00624 parser.error("You must specify the robot hostname")
00625 robot_hostname = args[0]
00626
00627
00628 global joint_offsets
00629 joint_offsets = load_joint_offsets(joint_names)
00630 rospy.logerr("Loaded calibration offsets: %s" % joint_offsets)
00631
00632
00633 global max_velocity
00634 max_velocity = rospy.get_param("~max_velocity", 0.5)
00635
00636
00637 server = TCPServer(("", 50001), CommanderTCPHandler)
00638 thread_commander = threading.Thread(name="CommanderHandler", target=server.serve_forever)
00639 thread_commander.daemon = True
00640 thread_commander.start()
00641
00642 with open(roslib.packages.get_pkg_dir('ur5_driver') + '/prog') as fin:
00643 program = fin.read() % {"driver_hostname": socket.getfqdn()}
00644 connection = UR5Connection(robot_hostname, PORT, program)
00645 connection.connect()
00646 connection.send_reset_program()
00647
00648 action_server = None
00649 try:
00650 while not rospy.is_shutdown():
00651
00652 if getConnectedRobot(wait=False):
00653 time.sleep(0.2)
00654 prevent_programming = rospy.get_param("prevent_programming", False)
00655 if prevent_programming:
00656 print "Programming now prevented"
00657 connection.send_reset_program()
00658 else:
00659 print "Disconnected. Reconnecting"
00660 if action_server:
00661 action_server.set_robot(None)
00662
00663 rospy.loginfo("Programming the robot")
00664 while True:
00665
00666 while not connection.ready_to_program():
00667 print "Waiting to program"
00668 time.sleep(1.0)
00669 prevent_programming = rospy.get_param("prevent_programming", False)
00670 connection.send_program()
00671
00672 r = getConnectedRobot(wait=True, timeout=1.0)
00673 if r:
00674 break
00675 rospy.loginfo("Robot connected")
00676
00677 if action_server:
00678 action_server.set_robot(r)
00679 else:
00680 action_server = UR5TrajectoryFollower(r, rospy.Duration(1.0))
00681 action_server.start()
00682
00683 except KeyboardInterrupt:
00684 try:
00685 r = getConnectedRobot(wait=False)
00686 rospy.signal_shutdown("KeyboardInterrupt")
00687 if r: r.send_quit()
00688 except:
00689 pass
00690 raise
00691
00692 if __name__ == '__main__': main()