joint_trajectory_action_controller.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 #
3 # Software License Agreement (BSD License)
4 #
5 # Copyright (c) 2010-2011, Antons Rebguns.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials provided
17 # with the distribution.
18 # * Neither the name of University of Arizona nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # POSSIBILITY OF SUCH DAMAGE.
34 
35 from __future__ import division
36 
37 
38 __author__ = 'Antons Rebguns'
39 __copyright__ = 'Copyright (c) 2010-2011 Antons Rebguns'
40 
41 __license__ = 'BSD'
42 __maintainer__ = 'Antons Rebguns'
43 __email__ = 'anton@email.arizona.edu'
44 
45 
46 from threading import Thread
47 
48 import rospy
49 import actionlib
50 
51 from std_msgs.msg import Float64
52 from trajectory_msgs.msg import JointTrajectory
53 from control_msgs.msg import FollowJointTrajectoryAction
54 from control_msgs.msg import FollowJointTrajectoryFeedback
55 from control_msgs.msg import FollowJointTrajectoryResult
56 
57 
58 class Segment():
59  def __init__(self, num_joints):
60  self.start_time = 0.0 # trajectory segment start time
61  self.duration = 0.0 # trajectory segment duration
62  self.positions = [0.0] * num_joints
63  self.velocities = [0.0] * num_joints
64 
66  def __init__(self, controller_namespace, controllers):
67  self.update_rate = 1000
69  self.trajectory = []
70 
71  self.controller_namespace = controller_namespace
72  self.joint_names = [c.joint_name for c in controllers]
73 
75  for c in controllers:
76  self.joint_to_controller[c.joint_name] = c
77 
78  self.port_to_joints = {}
79  for c in controllers:
80  if c.port_namespace not in self.port_to_joints: self.port_to_joints[c.port_namespace] = []
81  self.port_to_joints[c.port_namespace].append(c.joint_name)
82 
83  self.port_to_io = {}
84  for c in controllers:
85  if c.port_namespace in self.port_to_io: continue
86  self.port_to_io[c.port_namespace] = c.dxl_io
87 
88  self.joint_states = dict(zip(self.joint_names, [c.joint_state for c in controllers]))
89  self.num_joints = len(self.joint_names)
90  self.joint_to_idx = dict(zip(self.joint_names, range(self.num_joints)))
91 
92  def initialize(self):
93  ns = self.controller_namespace + '/joint_trajectory_action_node/constraints'
94  self.goal_time_constraint = rospy.get_param(ns + '/goal_time', 0.0)
95  self.stopped_velocity_tolerance = rospy.get_param(ns + '/stopped_velocity_tolerance', 0.01)
96  self.goal_constraints = []
98  self.min_velocity = rospy.get_param(self.controller_namespace + '/joint_trajectory_action_node/min_velocity', 0.1)
99 
100  for joint in self.joint_names:
101  self.goal_constraints.append(rospy.get_param(ns + '/' + joint + '/goal', -1.0))
102  self.trajectory_constraints.append(rospy.get_param(ns + '/' + joint + '/trajectory', -1.0))
103 
104  # Message containing current state for all controlled joints
105  self.msg = FollowJointTrajectoryFeedback()
106  self.msg.joint_names = self.joint_names
107  self.msg.desired.positions = [0.0] * self.num_joints
108  self.msg.desired.velocities = [0.0] * self.num_joints
109  self.msg.desired.accelerations = [0.0] * self.num_joints
110  self.msg.actual.positions = [0.0] * self.num_joints
111  self.msg.actual.velocities = [0.0] * self.num_joints
112  self.msg.error.positions = [0.0] * self.num_joints
113  self.msg.error.velocities = [0.0] * self.num_joints
114 
115  return True
116 
117 
118  def start(self):
119  self.running = True
120 
121  self.command_sub = rospy.Subscriber(self.controller_namespace + '/command', JointTrajectory, self.process_command)
122  self.state_pub = rospy.Publisher(self.controller_namespace + '/state', FollowJointTrajectoryFeedback, queue_size=1)
123  self.action_server = actionlib.SimpleActionServer(self.controller_namespace + '/follow_joint_trajectory',
124  FollowJointTrajectoryAction,
125  execute_cb=self.process_follow_trajectory,
126  auto_start=False)
127  self.action_server.start()
128  Thread(target=self.update_state).start()
129 
130  def stop(self):
131  self.running = False
132 
133  def process_command(self, msg):
134  if self.action_server.is_active(): self.action_server.set_preempted()
135 
136  while self.action_server.is_active():
137  rospy.sleep(0.01)
138 
139  self.process_trajectory(msg)
140 
141  def process_follow_trajectory(self, goal):
142  self.process_trajectory(goal.trajectory)
143 
144  def process_trajectory(self, traj):
145  num_points = len(traj.points)
146 
147  # make sure the joints in the goal match the joints of the controller
148  if set(self.joint_names) != set(traj.joint_names):
149  res = FollowJointTrajectoryResult()
150  res.error_code=FollowJointTrajectoryResult.INVALID_JOINTS
151  msg = 'Incoming trajectory joints do not match the joints of the controller'
152  rospy.logerr(msg)
153  rospy.logerr(' self.joint_names={%s}' % (set(self.joint_names)))
154  rospy.logerr(' traj.joint_names={%s}' % (set(traj.joint_names)))
155  self.action_server.set_aborted(result=res, text=msg)
156  return
157 
158  # make sure trajectory is not empty
159  if num_points == 0:
160  msg = 'Incoming trajectory is empty'
161  rospy.logerr(msg)
162  self.action_server.set_aborted(text=msg)
163  return
164 
165  # correlate the joints we're commanding to the joints in the message
166  # map from an index of joint in the controller to an index in the trajectory
167  lookup = [traj.joint_names.index(joint) for joint in self.joint_names]
168  durations = [0.0] * num_points
169 
170  # find out the duration of each segment in the trajectory
171  durations[0] = traj.points[0].time_from_start.to_sec()
172 
173  for i in range(1, num_points):
174  durations[i] = (traj.points[i].time_from_start - traj.points[i - 1].time_from_start).to_sec()
175 
176  if not traj.points[0].positions:
177  res = FollowJointTrajectoryResult()
178  res.error_code=FollowJointTrajectoryResult.INVALID_GOAL
179  msg = 'First point of trajectory has no positions'
180  rospy.logerr(msg)
181  self.action_server.set_aborted(result=res, text=msg)
182  return
183 
184  trajectory = []
185  time = rospy.Time.now() + rospy.Duration(0.01)
186 
187  for i in range(num_points):
188  seg = Segment(self.num_joints)
189 
190  if traj.header.stamp == rospy.Time(0.0):
191  seg.start_time = (time + traj.points[i].time_from_start).to_sec() - durations[i]
192  else:
193  seg.start_time = (traj.header.stamp + traj.points[i].time_from_start).to_sec() - durations[i]
194 
195  seg.duration = durations[i]
196 
197  # Checks that the incoming segment has the right number of elements.
198  if traj.points[i].velocities and len(traj.points[i].velocities) != self.num_joints:
199  res = FollowJointTrajectoryResult()
200  res.error_code=FollowJointTrajectoryResult.INVALID_GOAL
201  msg = 'Command point %d has %d elements for the velocities' % (i, len(traj.points[i].velocities))
202  rospy.logerr(msg)
203  self.action_server.set_aborted(result=res, text=msg)
204  return
205 
206  if len(traj.points[i].positions) != self.num_joints:
207  res = FollowJointTrajectoryResult()
208  res.error_code=FollowJointTrajectoryResult.INVALID_GOAL
209  msg = 'Command point %d has %d elements for the positions' % (i, len(traj.points[i].positions))
210  rospy.logerr(msg)
211  self.action_server.set_aborted(result=res, text=msg)
212  return
213 
214  for j in range(self.num_joints):
215  if traj.points[i].velocities:
216  seg.velocities[j] = traj.points[i].velocities[lookup[j]]
217  if traj.points[i].positions:
218  seg.positions[j] = traj.points[i].positions[lookup[j]]
219 
220  trajectory.append(seg)
221 
222  rospy.loginfo('Trajectory start requested at %.3lf, waiting...', traj.header.stamp.to_sec())
223  rate = rospy.Rate(self.update_rate)
224 
225  while traj.header.stamp > time:
226  time = rospy.Time.now()
227  rate.sleep()
228 
229  end_time = traj.header.stamp + rospy.Duration(sum(durations))
230  seg_end_times = [rospy.Time.from_sec(trajectory[seg].start_time + durations[seg]) for seg in range(len(trajectory))]
231 
232  rospy.loginfo('Trajectory start time is %.3lf, end time is %.3lf, total duration is %.3lf', time.to_sec(), end_time.to_sec(), sum(durations))
233 
234  self.trajectory = trajectory
235  traj_start_time = rospy.Time.now()
236 
237  for seg in range(len(trajectory)):
238  rospy.logdebug('current segment is %d time left %f cur time %f' % (seg, durations[seg] - (time.to_sec() - trajectory[seg].start_time), time.to_sec()))
239  rospy.logdebug('goal positions are: %s' % str(trajectory[seg].positions))
240 
241  # first point in trajectories calculated by OMPL is current position with duration of 0 seconds, skip it
242  if durations[seg] == 0:
243  rospy.logdebug('skipping segment %d with duration of 0 seconds' % seg)
244  continue
245 
246  multi_packet = {}
247 
248  for port, joints in self.port_to_joints.items():
249  vals = []
250 
251  for joint in joints:
252  j = self.joint_names.index(joint)
253 
254  start_position = self.joint_states[joint].current_pos
255  if seg != 0: start_position = trajectory[seg - 1].positions[j]
256 
257  desired_position = trajectory[seg].positions[j]
258  desired_velocity = max(self.min_velocity, abs(desired_position - start_position) / durations[seg])
259 
260  self.msg.desired.positions[j] = desired_position
261  self.msg.desired.velocities[j] = desired_velocity
262 
263  # probably need a more elegant way of figuring out if we are dealing with a dual controller
264  if hasattr(self.joint_to_controller[joint], "master_id"):
265  master_id = self.joint_to_controller[joint].master_id
266  slave_id = self.joint_to_controller[joint].slave_id
267  master_pos, slave_pos = self.joint_to_controller[joint].pos_rad_to_raw(desired_position)
268  spd = self.joint_to_controller[joint].spd_rad_to_raw(desired_velocity)
269  vals.append((master_id, master_pos, spd))
270  vals.append((slave_id, slave_pos, spd))
271  else:
272  motor_id = self.joint_to_controller[joint].motor_id
273  pos = self.joint_to_controller[joint].pos_rad_to_raw(desired_position)
274  spd = self.joint_to_controller[joint].spd_rad_to_raw(desired_velocity)
275  vals.append((motor_id, pos, spd))
276 
277  multi_packet[port] = vals
278 
279  for port, vals in multi_packet.items():
280  self.port_to_io[port].set_multi_position_and_speed(vals)
281 
282  while time < seg_end_times[seg]:
283  # check if new trajectory was received, if so abort current trajectory execution
284  # by setting the goal to the current position
285  if self.action_server.is_preempt_requested():
286  msg = 'New trajectory received. Aborting old trajectory.'
287  multi_packet = {}
288 
289  for port, joints in self.port_to_joints.items():
290  vals = []
291 
292  for joint in joints:
293  cur_pos = self.joint_states[joint].current_pos
294 
295  motor_id = self.joint_to_controller[joint].motor_id
296  pos = self.joint_to_controller[joint].pos_rad_to_raw(cur_pos)
297 
298  vals.append((motor_id, pos))
299 
300  multi_packet[port] = vals
301 
302  for port, vals in multi_packet.items():
303  self.port_to_io[port].set_multi_position(vals)
304 
305  self.action_server.set_preempted(text=msg)
306  rospy.logwarn(msg)
307  return
308 
309  rate.sleep()
310  time = rospy.Time.now()
311 
312  # Verifies trajectory constraints
313  for j, joint in enumerate(self.joint_names):
314  if self.trajectory_constraints[j] > 0 and self.msg.error.positions[j] > self.trajectory_constraints[j]:
315  res = FollowJointTrajectoryResult()
316  res.error_code=FollowJointTrajectoryResult.PATH_TOLERANCE_VIOLATED
317  msg = 'Unsatisfied position constraint for %s, trajectory point %d, %f is larger than %f' % \
318  (joint, seg, self.msg.error.positions[j], self.trajectory_constraints[j])
319  rospy.logwarn(msg)
320  self.action_server.set_aborted(result=res, text=msg)
321  return
322 
323  # let motors roll for specified amount of time
324  rospy.sleep(self.goal_time_constraint)
325 
326  for i, j in enumerate(self.joint_names):
327  rospy.logdebug('desired pos was %f, actual pos is %f, error is %f' % (trajectory[-1].positions[i], self.joint_states[j].current_pos, self.joint_states[j].current_pos - trajectory[-1].positions[i]))
328 
329  # Checks that we have ended inside the goal constraints
330  for (joint, pos_error, pos_constraint) in zip(self.joint_names, self.msg.error.positions, self.goal_constraints):
331  if pos_constraint > 0 and abs(pos_error) > pos_constraint:
332  res = FollowJointTrajectoryResult()
333  res.error_code=FollowJointTrajectoryResult.GOAL_TOLERANCE_VIOLATED
334  msg = 'Aborting because %s joint wound up outside the goal constraints, %f is larger than %f' % \
335  (joint, pos_error, pos_constraint)
336  rospy.logwarn(msg)
337  self.action_server.set_aborted(result=res, text=msg)
338  break
339  else:
340  msg = 'Trajectory execution successfully completed'
341  rospy.loginfo(msg)
342  res = FollowJointTrajectoryResult()
343  res.error_code=FollowJointTrajectoryResult.SUCCESSFUL
344  self.action_server.set_succeeded(result=res, text=msg)
345 
346  def update_state(self):
347  rate = rospy.Rate(self.state_update_rate)
348  while self.running and not rospy.is_shutdown():
349  self.msg.header.stamp = rospy.Time.now()
350 
351  # Publish current joint state
352  for i, joint in enumerate(self.joint_names):
353  state = self.joint_states[joint]
354  self.msg.actual.positions[i] = state.current_pos
355  self.msg.actual.velocities[i] = abs(state.velocity)
356  self.msg.error.positions[i] = self.msg.actual.positions[i] - self.msg.desired.positions[i]
357  self.msg.error.velocities[i] = self.msg.actual.velocities[i] - self.msg.desired.velocities[i]
358 
359  self.state_pub.publish(self.msg)
360  rate.sleep()


dynamixel_controllers
Author(s): Antons Rebguns, Cody Jorgensen, Cara Slutter
autogenerated on Tue May 12 2020 03:10:59