Skip to content

Commit 1da319b

Browse files
committed
Add support for execute joint trajectory
1 parent 6ab4820 commit 1da319b

File tree

3 files changed

+139
-47
lines changed

3 files changed

+139
-47
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Unreleased
2222

2323
* Added ``compas_fab.sensors.baumer.PosCon3D.reset()``
2424
* Added ``compas_fab.sensors.baumer.PosConCM.reset()``
25+
* ROS client: added support for MoveIt! execution action via `client.execute_joint_trajectory`.
2526

2627
**Removed**
2728

src/compas_fab/backends/ros/client.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
from roslibpy.actionlib import Goal
88

99
from compas_fab.backends.ros.exceptions import RosError
10+
from compas_fab.backends.ros.messages import ExecuteTrajectoryFeedback
11+
from compas_fab.backends.ros.messages import ExecuteTrajectoryGoal
12+
from compas_fab.backends.ros.messages import ExecuteTrajectoryResult
1013
from compas_fab.backends.ros.messages import FollowJointTrajectoryGoal
1114
from compas_fab.backends.ros.messages import FollowJointTrajectoryResult
1215
from compas_fab.backends.ros.messages import Header
1316
from compas_fab.backends.ros.messages import JointTrajectory as RosMsgJointTrajectory
1417
from compas_fab.backends.ros.messages import JointTrajectoryPoint as RosMsgJointTrajectoryPoint
18+
from compas_fab.backends.ros.messages import RobotTrajectory
1519
from compas_fab.backends.ros.messages import Time
1620
from compas_fab.backends.ros.planner_backend_moveit import MoveItPlanner
1721
from compas_fab.backends.tasks import CancellableTask
@@ -324,3 +328,64 @@ def handle_failure(error):
324328
goal.send(timeout=timeout)
325329

326330
return CancellableRosAction(goal)
331+
332+
def execute_joint_trajectory(self, joint_trajectory, action_name='/execute_trajectory', callback=None, errback=None, feedback_callback=None, timeout=60000):
333+
"""Execute a joint trajectory via the MoveIt infrastructure.
334+
335+
Note
336+
----
337+
This method does not support Multi-DOF Joint Trajectories.
338+
339+
Parameters
340+
----------
341+
joint_trajectory : :class:`compas_fab.robots.JointTrajectory`
342+
Instance of joint trajectory.
343+
callback : callable
344+
Function to be invoked when the goal is completed, requires
345+
one positional parameter ``result``.
346+
action_name : string
347+
ROS action name, defaults to ``/execute_trajectory``.
348+
errback : callable
349+
Function to be invoked in case of error or timeout, requires
350+
one position parameter ``exception``.
351+
feedback_callback : callable
352+
Function to be invoked during execution to provide feedback.
353+
timeout : int
354+
Timeout for goal completion in milliseconds.
355+
356+
Returns
357+
-------
358+
:class:`CancellableTask`
359+
An instance of a cancellable tasks.
360+
"""
361+
362+
joint_trajectory = self._convert_to_ros_trajectory(joint_trajectory)
363+
trajectory = RobotTrajectory(joint_trajectory=joint_trajectory)
364+
trajectory_goal = ExecuteTrajectoryGoal(trajectory=trajectory)
365+
366+
def handle_result(msg):
367+
result = ExecuteTrajectoryResult.from_msg(msg)
368+
callback(result)
369+
370+
def handle_feedback(msg):
371+
feedback = ExecuteTrajectoryFeedback.from_msg(msg)
372+
feedback_callback(feedback)
373+
374+
def handle_failure(error):
375+
errback(error)
376+
377+
action_client = ActionClient(self, action_name, 'moveit_msgs/ExecuteTrajectoryAction')
378+
goal = Goal(action_client, Message(trajectory_goal.msg))
379+
380+
if callback:
381+
goal.on('result', handle_result)
382+
383+
if feedback_callback:
384+
goal.on('feedback', handle_feedback)
385+
386+
if errback:
387+
goal.on('timeout', lambda: handle_failure(RosError("Action Goal timeout", -1)))
388+
389+
goal.send(timeout=timeout)
390+
391+
return CancellableRosAction(goal)

src/compas_fab/backends/ros/messages/moveit_msgs.py

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
from __future__ import absolute_import
22

3-
from compas_fab.backends.ros.messages.std_msgs import ROSmsg
4-
from compas_fab.backends.ros.messages.std_msgs import Header
53
from compas_fab.backends.ros.messages.geometry_msgs import Point
64
from compas_fab.backends.ros.messages.geometry_msgs import Pose
75
from compas_fab.backends.ros.messages.geometry_msgs import PoseStamped
8-
from compas_fab.backends.ros.messages.geometry_msgs import Vector3
96
from compas_fab.backends.ros.messages.geometry_msgs import Quaternion
10-
from compas_fab.backends.ros.messages.shape_msgs import SolidPrimitive
11-
from compas_fab.backends.ros.messages.shape_msgs import Plane
12-
from compas_fab.backends.ros.messages.shape_msgs import Mesh
7+
from compas_fab.backends.ros.messages.geometry_msgs import Vector3
8+
from compas_fab.backends.ros.messages.object_recognition_msgs import ObjectType
9+
from compas_fab.backends.ros.messages.octomap_msgs import OctomapWithPose
1310
from compas_fab.backends.ros.messages.sensor_msgs import JointState
1411
from compas_fab.backends.ros.messages.sensor_msgs import MultiDOFJointState
12+
from compas_fab.backends.ros.messages.shape_msgs import Mesh
13+
from compas_fab.backends.ros.messages.shape_msgs import Plane
14+
from compas_fab.backends.ros.messages.shape_msgs import SolidPrimitive
15+
from compas_fab.backends.ros.messages.std_msgs import Header
16+
from compas_fab.backends.ros.messages.std_msgs import ROSmsg
1517
from compas_fab.backends.ros.messages.trajectory_msgs import JointTrajectory
1618
from compas_fab.backends.ros.messages.trajectory_msgs import MultiDOFJointTrajectory
17-
from compas_fab.backends.ros.messages.object_recognition_msgs import ObjectType
18-
from compas_fab.backends.ros.messages.octomap_msgs import OctomapWithPose
1919

2020

2121
class CollisionObject(ROSmsg):
@@ -255,23 +255,25 @@ class PlannerParams(ROSmsg):
255255
"""
256256

257257
def __init__(self, keys=None, values=None, descriptions=None):
258-
self.keys = keys if keys else [] # parameter names (same size as values)
259-
self.values = values if values else [] # parameter values (same size as keys)
260-
self.descriptions = descriptions if descriptions else [] # parameter description (can be empty)
258+
self.keys = keys or [] # parameter names (same size as values)
259+
self.values = values or [] # parameter values (same size as keys)
260+
self.descriptions = descriptions or [] # parameter description (can be empty)
261+
261262

262263
class WorkspaceParameters(ROSmsg):
263264
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/WorkspaceParameters.html
264265
"""
265-
def __init__(self, header=Header(), min_corner=Vector3(-1000,-1000,-1000), max_corner=Vector3(1000,1000,1000)):
266+
def __init__(self, header=Header(), min_corner=Vector3(-1000, -1000, -1000), max_corner=Vector3(1000, 1000, 1000)):
266267
self.header = header
267268
self.min_corner = min_corner
268269
self.max_corner = max_corner
269270

271+
270272
class TrajectoryConstraints(ROSmsg):
271273
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/TrajectoryConstraints.html
272274
"""
273275
def __init__(self, constraints=None):
274-
self.constraints = constraints if constraints else [] #Constraints[]
276+
self.constraints = constraints or [] # Constraints[]
275277

276278

277279
class JointConstraint(ROSmsg):
@@ -291,21 +293,23 @@ def from_joint_constraint(cls, joint_constraint):
291293
c = joint_constraint
292294
return cls(c.joint_name, c.value, c.tolerance, c.tolerance, c.weight)
293295

296+
294297
class VisibilityConstraint(ROSmsg):
295298
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/VisibilityConstraint.html
296299
"""
297300
def __init__(self):
298301
raise NotImplementedError
299302

303+
300304
class BoundingVolume(ROSmsg):
301305
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/BoundingVolume.html
302306
"""
303307
def __init__(self, primitives=None, primitive_poses=None, meshes=None,
304308
mesh_poses=None):
305-
self.primitives = primitives if primitives else [] #shape_msgs/SolidPrimitive[]
306-
self.primitive_poses = primitive_poses if primitive_poses else [] #geometry_msgs/Pose[]
307-
self.meshes = meshes if meshes else [] #shape_msgs/Mesh[]
308-
self.mesh_poses = mesh_poses if mesh_poses else [] #geometry_msgs/Pose[]
309+
self.primitives = primitives or [] # shape_msgs/SolidPrimitive[]
310+
self.primitive_poses = primitive_poses or [] # geometry_msgs/Pose[]
311+
self.meshes = meshes or [] # shape_msgs/Mesh[]
312+
self.mesh_poses = mesh_poses or [] # geometry_msgs/Pose[]
309313

310314
@classmethod
311315
def from_box(cls, box):
@@ -360,16 +364,17 @@ def from_bounding_volume(cls, bounding_volume):
360364
else:
361365
raise NotImplementedError
362366

367+
363368
class PositionConstraint(ROSmsg):
364369
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/PositionConstraint.html
365370
"""
366371
def __init__(self, header=None, link_name=None, target_point_offset=None,
367372
constraint_region=None, weight=None):
368-
self.header = header if header else Header()
369-
self.link_name = link_name if link_name else ""
370-
self.target_point_offset = target_point_offset if target_point_offset else Vector3(0.,0.,0.) # geometry_msgs/Vector3
371-
self.constraint_region = constraint_region if constraint_region else BoundingVolume() # moveit_msgs/BoundingVolume
372-
self.weight = float(weight) if weight else 1.
373+
self.header = header or Header()
374+
self.link_name = link_name or ""
375+
self.target_point_offset = target_point_offset or Vector3(0., 0., 0.) # geometry_msgs/Vector3
376+
self.constraint_region = constraint_region or BoundingVolume() # moveit_msgs/BoundingVolume
377+
self.weight = float(weight) or 1.
373378

374379
@classmethod
375380
def from_position_constraint(cls, header, position_constraint):
@@ -378,6 +383,7 @@ def from_position_constraint(cls, header, position_constraint):
378383
constraint_region = BoundingVolume.from_bounding_volume(position_constraint.bounding_volume)
379384
return cls(header, position_constraint.link_name, None, constraint_region, position_constraint.weight)
380385

386+
381387
class OrientationConstraint(ROSmsg):
382388
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/msg/OrientationConstraint.html
383389
"""
@@ -393,9 +399,9 @@ def __init__(self, header=None, orientation=None, link_name=None,
393399
the z-axis by an angle of 6.3 radians, whereas the z-axis can only change
394400
by 0.01.
395401
"""
396-
self.header = header if header else Header()
397-
self.orientation = orientation or Quaternion()#geometry_msgs/Quaternion
398-
self.link_name = link_name if link_name else ""
402+
self.header = header or Header()
403+
self.orientation = orientation or Quaternion() # geometry_msgs/Quaternion
404+
self.link_name = link_name or ""
399405
self.absolute_x_axis_tolerance = float(absolute_x_axis_tolerance)
400406
self.absolute_y_axis_tolerance = float(absolute_y_axis_tolerance)
401407
self.absolute_z_axis_tolerance = float(absolute_z_axis_tolerance)
@@ -449,7 +455,7 @@ def human_readable(self):
449455
return ''
450456

451457

452-
class AllowedCollisionMatrix(ROSmsg):
458+
class AllowedCollisionMatrix(ROSmsg):
453459
"""http://docs.ros.org/melodic/api/moveit_msgs/html/msg/AllowedCollisionMatrix.html
454460
"""
455461
def __init__(self, entry_names=None, entry_values=None, default_entry_names=None, default_entry_values=None):
@@ -481,29 +487,16 @@ def __init__(self, name='', robot_state=None, robot_model_name='',
481487
fixed_frame_transforms=None, allowed_collision_matrix=None,
482488
link_padding=None, link_scale=None, object_colors=None, world=None,
483489
is_diff=False):
484-
self.name = name # string
485-
self.robot_state = robot_state or RobotState() # moveit_msgs/RobotState
486-
self.robot_model_name = robot_model_name # string
490+
self.name = name # string
491+
self.robot_state = robot_state or RobotState() # moveit_msgs/RobotState
492+
self.robot_model_name = robot_model_name # string
487493
self.fixed_frame_transforms = fixed_frame_transforms or [] # geometry_msgs/TransformStamped[]
488494
self.allowed_collision_matrix = allowed_collision_matrix or AllowedCollisionMatrix()
489-
self.link_padding = link_padding or [] # moveit_msgs/LinkPadding[]
490-
self.link_scale = link_scale or [] # moveit_msgs/LinkScale[]
491-
self.object_colors = object_colors or [] # moveit_msgs/ObjectColor[]
492-
self.world = world or PlanningSceneWorld() # moveit_msgs/PlanningSceneWorld
493-
self.is_diff = is_diff # bool
494-
495-
"""
496-
SCENE_SETTINGS = 1
497-
ROBOT_STATE=2
498-
ROBOT_STATE_ATTACHED_OBJECTS=4
499-
WORLD_OBJECT_NAMES=8
500-
WORLD_OBJECT_GEOMETRY=16
501-
OCTOMAP=32
502-
TRANSFORMS=64
503-
ALLOWED_COLLISION_MATRIX=128
504-
LINK_PADDING_AND_SCALING=256
505-
OBJECT_COLORS=512
506-
"""
495+
self.link_padding = link_padding or [] # moveit_msgs/LinkPadding[]
496+
self.link_scale = link_scale or [] # moveit_msgs/LinkScale[]
497+
self.object_colors = object_colors or [] # moveit_msgs/ObjectColor[]
498+
self.world = world or PlanningSceneWorld() # moveit_msgs/PlanningSceneWorld
499+
self.is_diff = is_diff # bool
507500

508501
@classmethod
509502
def from_msg(cls, msg):
@@ -515,3 +508,36 @@ def from_msg(cls, msg):
515508
msg['fixed_frame_transforms'], allowed_collision_matrix,
516509
msg['link_padding'], msg['link_scale'], msg['object_colors'],
517510
world, msg['is_diff'])
511+
512+
513+
class ExecuteTrajectoryGoal(ROSmsg):
514+
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/action/ExecuteTrajectory.html
515+
"""
516+
517+
def __init__(self, trajectory=None):
518+
self.trajectory = trajectory or RobotTrajectory()
519+
520+
521+
class ExecuteTrajectoryFeedback(ROSmsg):
522+
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/action/ExecuteTrajectory.html
523+
"""
524+
525+
def __init__(self, state=None):
526+
self.state = state
527+
528+
@classmethod
529+
def from_msg(cls, msg):
530+
return cls(msg['state'])
531+
532+
533+
class ExecuteTrajectoryResult(ROSmsg):
534+
"""http://docs.ros.org/kinetic/api/moveit_msgs/html/action/ExecuteTrajectory.html
535+
"""
536+
537+
def __init__(self, error_code=None):
538+
self.error_code = error_code or MoveItErrorCodes() # moveit_msgs/MoveItErrorCodes
539+
540+
@classmethod
541+
def from_msg(cls, msg):
542+
error_code = MoveItErrorCodes.from_msg(msg['error_code'])
543+
return cls(error_code)

0 commit comments

Comments
 (0)