WUT Velma robot API
rcprg_planner.py
Go to the documentation of this file.
1 
5 
6 # Copyright (c) 2018, Robot Control and Pattern Recognition Group, Warsaw University of Technology
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # * Neither the name of the Warsaw University of Technology nor the
17 # names of its contributors may be used to endorse or promote products
18 # derived from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 # DISCLAIMED. IN NO EVENT SHALL <COPYright HOLDER> BE LIABLE FOR ANY
24 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 import time
32 import rospy
33 import copy
34 import math
35 
36 import PyKDL
37 import tf
38 from moveit_msgs.msg import RobotTrajectory, JointConstraint, Constraints, MotionPlanRequest,\
39  OrientationConstraint
40 from moveit_msgs.srv import GetMotionPlan, GetMotionPlanRequest, ApplyPlanningScene,\
41  ApplyPlanningSceneRequest
42 from trajectory_msgs.msg import JointTrajectory
43 from threading import RLock
44 from octomap_msgs.msg import Octomap
45 import tf_conversions.posemath as pm
46 
47 def qMapToConstraints(q_map, tolerance=0.01, group=None):
48  """!
49  This function converts dictionary of joint positions to moveit_msgs.Constraints structure.
50 
51  @param q_map dictionary: A dictionary {name:position} of desired joint positions.
52  @param tolerance float: Tolerance of goal position.
53  @param group list of strings: Names of active joints in planning group.
54  Only constraints for these joints should be generated.
55  @return Returns filled moveit_msgs.Constraints structure.
56  """
57  result = Constraints()
58  for joint_name in q_map:
59  if group != None and not joint_name in group:
60  continue
61  constraint = JointConstraint()
62  constraint.joint_name = joint_name
63  constraint.position = q_map[ joint_name ]
64  constraint.tolerance_above = tolerance
65  constraint.tolerance_below = tolerance
66  constraint.weight = 1.0
67  result.joint_constraints.append( constraint )
68  return result
69 
70 #def endEffectorPoseToConstraints(effector_name, T_B_G, tolerance=0.01):
71 # assert (effector_name == "left" or effector_name=="right")
72 # result = Constraints()
73 # return result
74 
76  """!
77  Class used for obtaining occupancy map (as octomap) from octomap server.
78  """
79 
80  # Private method.
81  def _octomap_callback(self, data):
82  with self._lock:
83  self._octomap = data
84 
85  def __init__(self, topic="/octomap_binary"):
86  """!
87  Initialization, ROS topic subscription.
88 
89  @param topic string: octomap ROS topic.
90  """
91  self._lock = RLock()
92  self._octomap = None
93  rospy.Subscriber(topic, Octomap, self._octomap_callback)
94 
95  def getOctomap(self, timeout_s=None):
96  """!
97  Get current octomap from octomap server.
98 
99  @param timeout_s float: Timeout in seconds.
100 
101  @return Octomap object of type octomap_msgs.msg.Octomap, or None if timed out.
102  """
103  if timeout_s == None:
104  with self._lock:
105  if self._octomap:
106  return copy.copy(self._octomap)
107  return None
108  else:
109  time_start = time.time()
110  while not rospy.is_shutdown():
111  rospy.sleep(0.1)
112  with self._lock:
113  if self._octomap:
114  return copy.copy(self._octomap)
115  time_now = time.time()
116  if timeout_s and (time_now-time_start) > timeout_s:
117  return None
118  return None
119 
120 def wrapAngle(angle):
121  return (angle + math.pi) % (2 * math.pi) - math.pi
122 
123 def keepUprightIk(vec_E):
124  epsilon = 0.001
125  assert isinstance(vec_E, PyKDL.Vector)
126 
127  vec_E = copy.copy(vec_E)
128  vec_E.Normalize()
129  print('keepUprightIk(): vec_E: {}'.format(KDL2str(vec_E)))
130  if abs(vec_E.x()) < epsilon and abs(vec_E.y()) < epsilon:
131  q0 = 0.0
132  else:
133  q0 = wrapAngle( math.atan2(vec_E.y(), vec_E.x()) + math.pi/2 )
134 
135  T_E_U1 = PyKDL.Frame( PyKDL.Rotation.RotZ(q0), PyKDL.Vector() )
136  #T_B_U1 = T_B_E * T_E_U1
137  vec_U1 = T_E_U1.M.Inverse() * vec_E
138  print('vec_U1: {}'.format(KDL2str(vec_U1)))
139  if abs(vec_U1.y()) < epsilon and abs(vec_U1.z()) < epsilon:
140  q1 = 0.0
141  else:
142  q1 = wrapAngle( math.atan2(vec_U1.z(), vec_U1.y()) + math.pi/2 )
143 
144  T_U1_U2 = PyKDL.Frame( PyKDL.Rotation.RotY(math.pi/2)*PyKDL.Rotation.RotZ(q1), PyKDL.Vector() )
145  T_E_U2 = T_E_U1 * T_U1_U2
146  # Check the results
147  vec_E_check = T_E_U2 * PyKDL.Vector(1,0,0)
148  if (vec_E - vec_E_check).Norm() > 0.001:
149  raise Exception('Check did not pass: {} != {}'.format(KDL2str(vec_E), KDL2str(vec_E_check)))
150  return q0, q1
151 
152 # def keepUprightIk(T_B_E):
153 # epsilon = 0.001
154 # v_B = PyKDL.Vector(0, 0, 1)
155 # v_E = T_B_E.M.Inverse() * v_B
156 # print('v_E: {}'.format(v_E))
157 # if abs(v_E.x()) < epsilon and abs(v_E.y()) < epsilon:
158 # q0 = 0.0
159 # else:
160 # q0 = wrapAngle( math.atan2(v_E.y(), v_E.x()) + math.pi/2 )
161 
162 # T_E_U1 = PyKDL.Frame( PyKDL.Rotation.RotZ(q0), PyKDL.Vector() )
163 # T_B_U1 = T_B_E * T_E_U1
164 # v_U1 = T_B_U1.M.Inverse() * v_B
165 # print('v_U1: {}'.format(v_U1))
166 # if abs(v_U1.y()) < epsilon and abs(v_U1.z()) < epsilon:
167 # q1 = 0.0
168 # else:
169 # q1 = wrapAngle( math.atan2(v_U1.z(), v_U1.y()) + math.pi/2 )
170 
171 # T_U1_U2 = PyKDL.Frame( PyKDL.Rotation.RotY(math.pi/2)*PyKDL.Rotation.RotZ(q1), PyKDL.Vector() )
172 # T_B_U2 = T_B_U1 * T_U1_U2
173 # return q0, q1, T_B_U2
174 
175 # def printFrame(T):
176 # q = T.M.GetQuaternion()
177 # print('PyKDL.Frame(PyKDL.Rotation.Quaternion({}, {}, {}, {}), PyKDL.Vector({}, {}, {}))'.format(
178 # q[0], q[1], q[2], q[3], T.p.x(), T.p.y(), T.p.z()))
179 
180 def KDL2str(x):
181  if isinstance(x, PyKDL.Vector):
182  return 'PyKDL.Vector({}, {}, {})'.format(x.x(), x.y(), x.z())
183  elif isinstance(x, PyKDL.Rotation):
184  q = x.GetQuaternion()
185  return 'PyKDL.Rotation.Quaternion({}, {}, {}, {})'.format(q[0], q[1], q[2], q[3])
186  elif isinstance(x, PyKDL.Frame):
187  return 'PyKDL.Frame({}, {})'.format(KDL2str(x.M), KDL2str(x.p))
188  # else:
189  raise Exception('Not supported argument: "{}"'.format(x))
190 
191 class Planner:
192  """!
193  Class used as planner interface.
194  """
195  def plan(self, q_start, goal_constraints, group_name, attached_collision_objects=None, is_diff=False,
196  path_constraints=None, trajectory_constraints=None, workspace_parameters=None,
197  planner_id=None, num_planning_attempts=1,
198  allowed_planning_time=10.0, max_velocity_scaling_factor=1.0,
199  max_acceleration_scaling_factor=1.0, keep_upright=None):
200  """!
201  Plan motion in joint space.
202 
203  @param q_start dictionary: A dictionary {name:position} of starting configuration.
204  @param goal_constraints list: A list of goal constraints of type moveit_msgs.msg.Constraints.
205  @param group_name string: Name of joint group to perform planning on.
206  @param attached_collision_objects list: Objects attached to robot represented as a list of objects
207  of type moveit_msgs.msg.AttachedCollisionObject.
208  @param is_diff
209  @param path_constraints Path constraints of type moveit_msgs.msg.Constraints.
210  @param trajectory_constraints Trajectory constraints of type moveit_msgs.msg.TrajectoryConstraints.
211  @param workspace_parameters Parameters of robots workspace of type moveit_msgs.msg.WorkspaceParameters.
212  @param planner_id string: Name of planning algorithm.
213  @param num_planning_attempts int: Maximum number of planning attempts.
214  @param allowed_planning_time float: Maximum allowed time for planning.
215  @param max_velocity_scaling_factor float: Scaling factor for velocity: the bigger, the faster robot moves.
216  @param max_acceleration_scaling_factor float: Scaling factor for acceleration: the bigger, the faster robot accelerates.
217 
218  @return Returns trajectory_msgs.msg.JointTrajectory.
219  """
220  req = MotionPlanRequest()
221 
222  q_start = copy.copy(q_start)
223  if not 'leftKeepUprightJoint0' in q_start:
224  q_start['leftKeepUprightJoint0'] = 0.0
225  if not 'leftKeepUprightJoint1' in q_start:
226  q_start['leftKeepUprightJoint1'] = 0.0
227  if not 'rightKeepUprightJoint0' in q_start:
228  q_start['rightKeepUprightJoint0'] = 0.0
229  if not 'rightKeepUprightJoint1' in q_start:
230  q_start['rightKeepUprightJoint1'] = 0.0
231 
232  for joint_name in sorted(q_start.keys()):
233  req.start_state.joint_state.name.append( joint_name )
234  req.start_state.joint_state.position.append( q_start[joint_name] )
235 
236  if attached_collision_objects:
237  print('Planner.plan(): there are some objects attached to grippers')
238  req.start_state.attached_collision_objects = attached_collision_objects
239 
240  req.start_state.is_diff = is_diff
241 
242  req.goal_constraints = goal_constraints
243 
244  req.group_name = group_name
245 
246  if path_constraints:
247  req.path_constraints = path_constraints
248 
249  if trajectory_constraints:
250  req.trajectory_constraints = trajectory_constraints
251 
252  if workspace_parameters:
253  req.workspace_parameters = workspace_parameters
254 
255  if planner_id:
256  req.planner_id = planner_id
257 
258  if not keep_upright is None:
259  current_time = rospy.Time.now()
260  for side_str, vec_E in keep_upright.iteritems():
261  assert isinstance(vec_E, PyKDL.Vector)
262 
263  q_ku0, q_ku1 = keepUprightIk(vec_E)
264  #print('keep upright ik: {}, {}'.format(start_q['leftKeepUprightJoint0'], start_q['leftKeepUprightJoint1']))
265 
266  q_ku0_name = '{}KeepUprightJoint0'.format(side_str)
267  q_ku1_name = '{}KeepUprightJoint1'.format(side_str)
268 
269  # Update keep upright joints
270  for q_idx, joint_name in enumerate(req.start_state.joint_state.name):
271  if joint_name == q_ku0_name:
272  req.start_state.joint_state.position[q_idx] = q_ku0
273  elif joint_name == q_ku1_name:
274  req.start_state.joint_state.position[q_idx] = q_ku1
275 
276  for goal_constr in req.goal_constraints:
277  for joint_constr in goal_constr.joint_constraints:
278  if joint_constr.joint_name == q_ku0_name:
279  joint_constr.position = q_ku0
280  elif joint_constr.joint_name == q_ku1_name:
281  joint_constr.position = q_ku1
282 
283  dbg_str = '{'
284  for q_idx, joint_name in enumerate(req.start_state.joint_state.name):
285  dbg_str += '\'{}\':{},'.format(joint_name,
286  req.start_state.joint_state.position[q_idx])
287  dbg_str += '}'
288  print('Planner.plan(): start_q: {}'.format(dbg_str))
289  print('Planner.plan(): upright {} vec_E: {}'.format(side_str, KDL2str(vec_E)))
290  #print('Planner.plan(): T_B_U2: {}'.format(KDL2str(T_B_U2)))
291 
292  # This message contains the definition of an orientation constraint.
293  ori_constr = OrientationConstraint()
294  ori_constr.header.frame_id = 'world'
295 
296  # The robot link this constraint refers to
297  ori_constr.link_name = '{}_keepUprightLink2'.format( side_str )
298 
299  # The desired orientation of the robot link specified as a quaternion
300  qx, qy, qz, qw = PyKDL.Rotation.RotY(math.radians(-90.0)).GetQuaternion()
301  ori_constr.orientation.x = qx
302  ori_constr.orientation.y = qy
303  ori_constr.orientation.z = qz
304  ori_constr.orientation.w = qw
305 
306  # Tolerance on the three vector components of the orientation error (optional)
307  ori_constr.absolute_x_axis_tolerance = math.radians(200.0)
308  ori_constr.absolute_y_axis_tolerance = math.radians(40.0)
309  ori_constr.absolute_z_axis_tolerance = math.radians(40.0)
310 
311  # A weighting factor for this constraint (denotes relative importance to other
312  # constraints. Closer to zero means less important)
313  ori_constr.weight = 1.0
314 
315  req.path_constraints.orientation_constraints.append( ori_constr )
316 
317  ori_constr_goal = copy.copy(ori_constr)
318  ori_constr_goal.absolute_x_axis_tolerance = math.radians(200.0)
319  ori_constr_goal.absolute_y_axis_tolerance = math.radians(30.0)
320  ori_constr_goal.absolute_z_axis_tolerance = math.radians(30.0)
321 
322  # Add orientation constraint for upright pose to all goals
323  for goal_constr in req.goal_constraints:
324  goal_constr.orientation_constraints.append(ori_constr_goal)
325 
326  req.num_planning_attempts = num_planning_attempts
327 
328  req.allowed_planning_time = allowed_planning_time
329 
330  req.max_velocity_scaling_factor = max_velocity_scaling_factor
331 
332  req.max_acceleration_scaling_factor = max_acceleration_scaling_factor
333 
334  #print('*** Planning request: ***')
335  #print(req)
336 
337  request = GetMotionPlanRequest()
338  request.motion_plan_request = req
339 
340  try:
341  res = self.plan_service( request ).motion_plan_response
342  except rospy.service.ServiceException as e:
343  print "Planner.plan(): could not plan:"
344  print e
345  return None
346 
347  #print('*** Planning response: ***')
348  #print(res)
349 
350  if not res:
351  return None
352 
353  if not self._max_traj_len is None and\
354  len(res.trajectory.joint_trajectory.points) > self._max_traj_len:
355  print('Planner.plan(): Trajectory is too long: {}'.format(
356  len(res.trajectory.joint_trajectory.points) ))
357  return None
358 
359  print('Found trajectory for joints: {}'.format(res.trajectory.joint_trajectory.joint_names))
360  return res.trajectory.joint_trajectory
361 
362  def processWorld(self, octomap):
363  """!
364  Updates occupancy map of the planner.
365 
366  @param octomap octomap_msgs.msg.Octomap: Occupancy map.
367 
368  @return Returns True if the octomap was succesfully loaded, False otherwise.
369  """
370  req = ApplyPlanningSceneRequest()
371  #TODO: req.scene.name
372  #TODO: req.scene.robot_state
373  #TODO: req.scene.robot_model_name
374  #TODO: req.scene.fixed_frame_transforms
375  #TODO: req.scene.allowed_collision_matrix
376  #TODO: req.scene.link_padding
377  #TODO: req.scene.link_scale
378  #TODO: req.scene.object_colors
379  #TODO: req.scene.world.collision_objects
380 
381  req.scene.world.octomap.header.frame_id = "world"
382  req.scene.world.octomap.octomap = octomap
383  req.scene.is_diff = False
384  res = self.processWorld_service(req)
385  if not res.success:
386  return False
387  return True
388 
389  def __init__(self, max_traj_len):
390  """!
391  Initialization of Python planner interface.
392 
393  @param max_traj_len int: Maximum number of nodes in a planned trajectory.
394  """
395  self._max_traj_len = max_traj_len
396  self._listener = tf.TransformListener()
397 
398  def waitForInit(self, timeout_s=None):
399  """!
400  Wait until planner interface is not initialized.
401 
402  @param timeout_s float: Timeout in seconds.
403 
404  @return Returns True if the Python planner interface was succesfully initialized, False otherwise.
405  """
406  try:
407  rospy.wait_for_service('/rcprg_planner/plan', timeout=timeout_s)
408  self.plan_service = rospy.ServiceProxy('/rcprg_planner/plan', GetMotionPlan)
409  except:
410  return False
411 
412  try:
413  rospy.wait_for_service('/rcprg_planner/processWorld', timeout=timeout_s)
414  self.processWorld_service = rospy.ServiceProxy('/rcprg_planner/processWorld', ApplyPlanningScene)
415  except:
416  return False
417  return True
418 
def qMapToConstraints(q_map, tolerance=0.01, group=None)
This function converts dictionary of joint positions to moveit_msgs.Constraints structure.
def waitForInit(self, timeout_s=None)
Wait until planner interface is not initialized.
def processWorld(self, octomap)
Updates occupancy map of the planner.
Class used for obtaining occupancy map (as octomap) from octomap server.
def __init__(self, max_traj_len)
Initialization of Python planner interface.
def getOctomap(self, timeout_s=None)
Get current octomap from octomap server.
def plan(self, q_start, goal_constraints, group_name, attached_collision_objects=None, is_diff=False, path_constraints=None, trajectory_constraints=None, workspace_parameters=None, planner_id=None, num_planning_attempts=1, allowed_planning_time=10.0, max_velocity_scaling_factor=1.0, max_acceleration_scaling_factor=1.0, keep_upright=None)
Plan motion in joint space.
Class used as planner interface.
def __init__(self, topic="/octomap_binary")
Initialization, ROS topic subscription.