Skip to content

Commit c844e41

Browse files
committed
Add support for compas_fab.robots.JointTrajectory on ROS trajectory follower
1 parent b471af5 commit c844e41

File tree

5 files changed

+88
-63
lines changed

5 files changed

+88
-63
lines changed

src/compas_fab/backends/ros/client.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from compas_fab.backends.ros.messages import FollowJointTrajectoryGoal
1111
from compas_fab.backends.ros.messages import FollowJointTrajectoryResult
1212
from compas_fab.backends.ros.messages import Header
13-
from compas_fab.backends.ros.messages import JointTrajectory
14-
from compas_fab.backends.ros.messages import JointTrajectoryPoint
13+
from compas_fab.backends.ros.messages import JointTrajectory as RosMsgJointTrajectory
14+
from compas_fab.backends.ros.messages import JointTrajectoryPoint as RosMsgJointTrajectoryPoint
1515
from compas_fab.backends.ros.messages import Time
1616
from compas_fab.backends.ros.planner_backend_moveit import MoveItPlanner
1717
from compas_fab.backends.tasks import CancellableTask
@@ -81,7 +81,6 @@ def __init__(self, host='localhost', port=9090, is_secure=False, planner_backend
8181
planner_backend_type = PLANNER_BACKENDS[planner_backend]
8282
self.__class__ = type('RosClient_' + planner_backend_type.__name__, (planner_backend_type, RosClient), {})
8383

84-
8584
def __enter__(self):
8685
self.run()
8786
self.connect()
@@ -227,7 +226,7 @@ def remove_attached_collision_mesh(self, id):
227226
def get_configuration(self):
228227
pass
229228

230-
def follow_configurations(self, callback, joint_names, configurations, timesteps, timeout=None):
229+
def follow_configurations(self, callback, joint_names, configurations, timesteps, timeout=60000):
231230

232231
if len(configurations) != len(timesteps):
233232
raise ValueError("%d configurations must have %d timesteps, but %d given." % (
@@ -239,21 +238,44 @@ def follow_configurations(self, callback, joint_names, configurations, timesteps
239238
points = []
240239
num_joints = len(configurations[0].values)
241240
for config, time in zip(configurations, timesteps):
242-
pt = JointTrajectoryPoint(positions=config.values, velocities=[
241+
pt = RosMsgJointTrajectoryPoint(positions=config.values, velocities=[
243242
0]*num_joints, time_from_start=Time(secs=(time)))
244243
points.append(pt)
245244

246-
joint_trajectory = JointTrajectory(
245+
joint_trajectory = RosMsgJointTrajectory(
247246
Header(), joint_names, points) # specify header necessary?
248-
return self.follow_joint_trajectory(callback, joint_trajectory, timeout)
247+
return self.follow_joint_trajectory(joint_trajectory=joint_trajectory, callback=callback, timeout=timeout)
248+
249+
def _convert_to_ros_trajectory(self, joint_trajectory):
250+
"""Converts a ``compas_fab.robots.JointTrajectory`` into a ROS Msg joint trajectory."""
251+
252+
# For backwards-compatibility, accept ROS Msg directly as well and simply do not modify
253+
if isinstance(joint_trajectory, RosMsgJointTrajectory):
254+
return joint_trajectory
255+
256+
trajectory = RosMsgJointTrajectory()
257+
trajectory.joint_names = joint_trajectory.joint_names
258+
259+
for point in joint_trajectory.points:
260+
ros_point = RosMsgJointTrajectoryPoint(
261+
positions=point.positions,
262+
velocities=point.velocities,
263+
accelerations=point.accelerations,
264+
effort=point.effort,
265+
time_from_start=Time(
266+
point.time_from_start.secs, point.time_from_start.nsecs),
267+
)
268+
trajectory.points.append(ros_point)
269+
270+
return trajectory
249271

250272
def follow_joint_trajectory(self, joint_trajectory, action_name='/joint_trajectory_action', callback=None, errback=None, feedback_callback=None, timeout=60000):
251273
"""Follow the joint trajectory as computed by MoveIt planner.
252274
253275
Parameters
254276
----------
255-
joint_trajectory : JointTrajectory
256-
Joint trajectory message as computed by MoveIt planner
277+
joint_trajectory : :class:`compas_fab.robots.JointTrajectory`
278+
Instance of joint trajectory. Note: for backwards compatibility, this supports a ROS Msg being passed as well.
257279
action_name : string
258280
ROS action name, defaults to ``/joint_trajectory_action`` but some drivers need ``/follow_joint_trajectory``.
259281
callback : callable
@@ -273,8 +295,8 @@ def follow_joint_trajectory(self, joint_trajectory, action_name='/joint_trajecto
273295
An instance of a cancellable tasks.
274296
"""
275297

276-
trajectory_goal = FollowJointTrajectoryGoal(
277-
trajectory=joint_trajectory)
298+
joint_trajectory = self._convert_to_ros_trajectory(joint_trajectory)
299+
trajectory_goal = FollowJointTrajectoryGoal(trajectory=joint_trajectory)
278300

279301
def handle_result(msg, client):
280302
result = FollowJointTrajectoryResult.from_msg(msg)
@@ -302,4 +324,3 @@ def handle_failure(error):
302324
goal.send(timeout=timeout)
303325

304326
return CancellableRosAction(goal)
305-

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

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,52 +12,52 @@ class JointTolerance(ROSmsg):
1212
"""
1313
def __init__(self, name="", position=0., velocity=0., acceleration=0.):
1414
self.name = name
15-
self.position = position # in radians or meters (for a revolute or prismatic joint, respectively)
16-
self.velocity = velocity # in rad/sec or m/sec
17-
self.acceleration = acceleration # in rad/sec^2 or m/sec^2
15+
self.position = position # in radians or meters (for a revolute or prismatic joint, respectively)
16+
self.velocity = velocity # in rad/sec or m/sec
17+
self.acceleration = acceleration # in rad/sec^2 or m/sec^2
1818

1919

2020
class FollowJointTrajectoryGoal(ROSmsg):
2121
"""http://docs.ros.org/fuerte/api/control_msgs/html/msg/FollowJointTrajectoryGoal.html
2222
"""
2323

24-
def __init__(self, trajectory=None, path_tolerance=None,
24+
def __init__(self, trajectory=None, path_tolerance=None,
2525
goal_tolerance=None, goal_time_tolerance=None):
26-
self.trajectory = trajectory if trajectory else JointTrajectory() #trajectory_msgs/JointTrajectory
27-
self.path_tolerance = path_tolerance if path_tolerance else []#control_msgs/JointTolerance[]
28-
self.goal_tolerance = goal_tolerance if goal_tolerance else [] #control_msgs/JointTolerance[]
29-
self.goal_time_tolerance = goal_time_tolerance if goal_time_tolerance else Time(secs=1.)
26+
self.trajectory = trajectory or JointTrajectory() # trajectory_msgs/JointTrajectory
27+
self.path_tolerance = path_tolerance or [] # control_msgs/JointTolerance[]
28+
self.goal_tolerance = goal_tolerance or [] # control_msgs/JointTolerance[]
29+
self.goal_time_tolerance = goal_time_tolerance or Time(secs=1.)
3030

3131

3232
class FollowJointTrajectoryActionGoal(ROSmsg):
3333
"""http://docs.ros.org/fuerte/api/control_msgs/html/msg/FollowJointTrajectoryActionGoal.html
3434
"""
3535

3636
def __init__(self, header=None, goal_id=None, goal=None):
37-
self.header = header if header else Header()
38-
self.goal_id = goal_id if goal_id else GoalID() #actionlib_msgs/GoalID goal_id
39-
self.goal = goal if goal else FollowJointTrajectoryGoal() # FollowJointTrajectoryGoal goal
37+
self.header = header or Header()
38+
self.goal_id = goal_id or GoalID() # actionlib_msgs/GoalID goal_id
39+
self.goal = goal or FollowJointTrajectoryGoal() # FollowJointTrajectoryGoal goal
4040

4141

4242
class FollowJointTrajectoryFeedback(ROSmsg):
4343
"""http://docs.ros.org/fuerte/api/control_msgs/html/msg/FollowJointTrajectoryFeedback.html
4444
"""
4545
def __init__(self, header=None, joint_names=None, desired=None, actual=None,
4646
error=None):
47-
self.header = header if header else Header()
48-
self.joint_names = joint_names if joint_names else []
49-
self.desired = desired if desired else JointTrajectoryPoint()
50-
self.actual = actual if actual else JointTrajectoryPoint()
51-
self.error = error if error else JointTrajectoryPoint()
47+
self.header = header or Header()
48+
self.joint_names = joint_names or []
49+
self.desired = desired or JointTrajectoryPoint()
50+
self.actual = actual or JointTrajectoryPoint()
51+
self.error = error or JointTrajectoryPoint()
5252

5353

5454
class FollowJointTrajectoryActionFeedback(ROSmsg):
5555
"""http://docs.ros.org/fuerte/api/control_msgs/html/msg/FollowJointTrajectoryActionFeedback.html
5656
"""
5757
def __init__(self, header=None, status=None, feedback=None):
58-
self.header = header if header else Header()
59-
self.status = status if status else GoalStatus()
60-
self.feedback = feedback if feedback else FollowJointTrajectoryFeedback()
58+
self.header = header or Header()
59+
self.status = status or GoalStatus()
60+
self.feedback = feedback or FollowJointTrajectoryFeedback()
6161

6262

6363
class FollowJointTrajectoryResult(ROSmsg):
@@ -74,7 +74,7 @@ class FollowJointTrajectoryResult(ROSmsg):
7474
def __init__(self, error_code=0, error_string=""):
7575
self.error_code = error_code
7676
self.error_string = error_string
77-
77+
7878
@classmethod
7979
def from_msg(cls, msg):
8080
error_code = msg['error_code']
@@ -93,14 +93,13 @@ class FollowJointTrajectoryActionResult(ROSmsg):
9393
"""http://docs.ros.org/fuerte/api/control_msgs/html/msg/FollowJointTrajectoryActionResult.html
9494
"""
9595
def __init__(self, header=None, status=None, result=None):
96-
self.header = header if header else Header()
97-
self.status = status if status else GoalStatus()
98-
self.result = result if result else FollowJointTrajectoryResult()
99-
96+
self.header = header or Header()
97+
self.status = status or GoalStatus()
98+
self.result = result or FollowJointTrajectoryResult()
99+
100100
@classmethod
101101
def from_msg(cls, msg):
102102
header = Header.from_msg(msg['header'])
103103
status = GoalStatus.from_msg(msg['status'])
104104
result = FollowJointTrajectoryResult.from_msg(msg['result'])
105105
return cls(header, status, result)
106-

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

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@
44
from .std_msgs import Header
55
from .std_msgs import Time
66

7-
from .geometry_msgs import Transform
8-
from .geometry_msgs import Twist
9-
107

118
class JointTrajectoryPoint(ROSmsg):
129
"""http://docs.ros.org/kinetic/api/trajectory_msgs/html/msg/JointTrajectoryPoint.html
1310
"""
1411

1512
def __init__(self, positions=None, velocities=None, accelerations=None, effort=None, time_from_start=None):
16-
self.positions = positions if positions else []
17-
self.velocities = velocities if velocities else []
18-
self.accelerations = accelerations if accelerations else []
19-
self.effort = effort if effort else []
20-
self.time_from_start = time_from_start if time_from_start else Time()
13+
self.positions = positions or []
14+
self.velocities = velocities or []
15+
self.accelerations = accelerations or []
16+
self.effort = effort or []
17+
self.time_from_start = time_from_start or Time()
2118
# TODO: check if we need to enter zeros to all
2219

2320
@classmethod
@@ -40,9 +37,9 @@ class JointTrajectory(ROSmsg):
4037
"""
4138

4239
def __init__(self, header=None, joint_names=None, points=None):
43-
self.header = header if header else Header()
44-
self.joint_names = joint_names if joint_names else []
45-
self.points = points if points else []
40+
self.header = header or Header()
41+
self.joint_names = joint_names or []
42+
self.points = points or []
4643

4744
@classmethod
4845
def from_msg(cls, msg):
@@ -57,20 +54,20 @@ class MultiDOFJointTrajectoryPoint(ROSmsg):
5754
"""
5855

5956
def __init__(self, transforms=None, velocities=None, accelerations=None, time_from_start=None):
60-
self.transforms = transforms if transforms else [] # geometry_msgs/Transform[]
61-
self.velocities = velocities if velocities else [] # geometry_msgs/Twist[]
62-
self.accelerations = accelerations if accelerations else [] # geometry_msgs/Twist[]
63-
self.time_from_start = time_from_start if time_from_start else Time()
57+
self.transforms = transforms or [] # geometry_msgs/Transform[]
58+
self.velocities = velocities or [] # geometry_msgs/Twist[]
59+
self.accelerations = accelerations or [] # geometry_msgs/Twist[]
60+
self.time_from_start = time_from_start or Time()
6461

6562

6663
class MultiDOFJointTrajectory(ROSmsg):
6764
"""http://docs.ros.org/kinetic/api/trajectory_msgs/html/msg/MultiDOFJointTrajectory.html
6865
"""
6966

7067
def __init__(self, header=None, joint_names=None, points=None):
71-
self.header = header if header else Header()
72-
self.joint_names = joint_names if joint_names else []
73-
self.points = points if points else []
68+
self.header = header or Header()
69+
self.joint_names = joint_names or []
70+
self.points = points or []
7471

7572
@classmethod
7673
def from_msg(cls, msg):

src/compas_fab/backends/ros/planner_backend_moveit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def forward_kinematics_async(self, callback, errback, joint_positions, base_link
151151
def plan_cartesian_motion_async(self, callback, errback, frames, base_link,
152152
ee_link, group, joint_names, joint_types,
153153
start_configuration, max_step, jump_threshold,
154-
avoid_collisions, path_constraints,
154+
avoid_collisions, path_constraints,
155155
attached_collision_meshes):
156156
"""Asynchronous handler of MoveIt cartesian motion planner service."""
157157
header = Header(frame_id=base_link)
@@ -179,6 +179,7 @@ def convert_to_trajectory(response):
179179
trajectory = JointTrajectory()
180180
trajectory.source_message = response
181181
trajectory.fraction = response.fraction
182+
trajectory.joint_names = response.solution.joint_trajectory.joint_names
182183
trajectory.points = convert_trajectory_points(response.solution.joint_trajectory.points, joint_types)
183184
trajectory.start_configuration = Configuration(response.start_state.joint_state.position, start_configuration.types)
184185

@@ -260,6 +261,7 @@ def convert_to_trajectory(response):
260261
trajectory = JointTrajectory()
261262
trajectory.source_message = response
262263
trajectory.fraction = 1.
264+
trajectory.joint_names = response.trajectory.joint_trajectory.joint_names
263265
trajectory.points = convert_trajectory_points(response.trajectory.joint_trajectory.points, joint_types)
264266
trajectory.start_configuration = Configuration(response.trajectory_start.joint_state.position, start_configuration.types)
265267
trajectory.planning_time = response.planning_time

src/compas_fab/robots/trajectory.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,21 @@ class JointTrajectory(Trajectory):
140140
141141
Attributes
142142
----------
143-
points: :obj:`list` of :class:`JointTrajectoryPoint`
143+
trajectory_points: :obj:`list` of :class:`JointTrajectoryPoint`
144144
List of points composing the trajectory.
145+
joint_names: :obj:`list` of :obj:`str`
146+
List of joint names of the trajectory.
145147
start_configuration: :class:`Configuration`
146148
Start configuration for the trajectory.
147149
fraction: float
148150
Indicates the percentage of requested trajectory that was calcuted,
149151
e.g. ``1`` means the full trajectory was found.
150152
"""
151153

152-
def __init__(self, trajectory_points=[], start_configuration=None, fraction=None):
154+
def __init__(self, trajectory_points=[], joint_names=[], start_configuration=None, fraction=None):
153155
super(Trajectory, self).__init__()
154156
self.points = trajectory_points
157+
self.joint_names = joint_names
155158
self.start_configuration = start_configuration
156159
self.fraction = fraction
157160

@@ -181,15 +184,18 @@ def to_data(self):
181184
@property
182185
def data(self):
183186
""":obj:`dict` : The data representing the trajectory."""
184-
return {
185-
'points': [p.to_data() for p in self.points],
186-
'start_configuration': self.start_configuration.to_data(),
187-
'fraction': self.fraction,
188-
}
187+
data_obj = {}
188+
data_obj['points'] = [p.to_data() for p in self.points]
189+
data_obj['joint_names'] = self.joint_names or []
190+
data_obj['start_configuration'] = self.start_configuration.to_data()
191+
data_obj['fraction'] = self.fraction
192+
193+
return data_obj
189194

190195
@data.setter
191196
def data(self, data):
192197
self.points = list(map(JointTrajectoryPoint.from_data, data.get('points') or []))
198+
self.joint_names = data.get('joint_names', [])
193199
self.start_configuration = Configuration.from_data(data.get('start_configuration'))
194200
self.fraction = data.get('fraction')
195201

0 commit comments

Comments
 (0)