joint_trajectory_action_controller.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Software License Agreement (BSD License)
5 #
6 # Copyright (c) 2017 Svenzva Robotics, 2010-2011, Antons Rebguns.
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
11 # are met:
12 #
13 # * Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
15 # * Redistributions in binary form must reproduce the above
16 # copyright notice, this list of conditions and the following
17 # disclaimer in the documentation and/or other materials provided
18 # with the distribution.
19 # * Neither the name of University of Arizona, Svenzva Robotics LLC,
20 # nor the names of its contributors may be used to endorse or
21 # promote products derived from this software without specific
22 # prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 # POSSIBILITY OF SUCH DAMAGE.
36 
37 from __future__ import division
38 from threading import Thread
39 
40 import rospy
41 import actionlib
42 
43 from std_msgs.msg import Float64
44 from svenzva_msgs.msg import MotorState, MotorStateList
45 from trajectory_msgs.msg import JointTrajectory
46 from control_msgs.msg import FollowJointTrajectoryAction
47 from control_msgs.msg import FollowJointTrajectoryFeedback
48 from control_msgs.msg import FollowJointTrajectoryResult
49 from svenzva_drivers.svenzva_driver import SvenzvaDriver
50 
51 class Segment():
52  def __init__(self, num_joints):
53  self.start_time = 0.0 # trajectory segment start time
54  self.duration = 0.0 # trajectory segment duration
55  self.positions = [0.0] * num_joints
56  self.velocities = [0.0] * num_joints
57 
59  def __init__(self, controller_namespace, mx_io, states):
60  #set default values
61  self.update_rate = 1000
63  self.trajectory = []
64 
65  self.mx_io = mx_io
66  self.num_joints = 6
67  self.controller_namespace = controller_namespace
68 
69  self.joint_names = rospy.get_param('joint_names', ['joint_1', 'joint_2', 'joint_3', 'joint_4', 'joint_5', 'joint_6'])
70 
71  #trajectory controller does not control finger movements
72  #if finger joints specified, remove them
73  if 'finger_joint_1' in self.joint_names:
74  self.joint_names.remove('finger_joint_1')
75  if 'finger_joint_2' in self.joint_names:
76  self.joint_names.remove('finger_joint_2')
77 
78  rospy.Subscriber("revel/motor_states", MotorStateList, self.motor_state_cb, queue_size=1)
79 
80  self.motor_states = []
81  self.joint_to_id = dict(zip(self.joint_names, range(1, self.num_joints+1)))
82  self.gear_ratios = [4,7,7,3,4,1]
83  self.num_joints = len(self.joint_names)
84  self.initialize()
85 
86  def initialize(self):
87  ns = self.controller_namespace + '/joint_trajectory_action_node/constraints'
88  self.goal_time_constraint = rospy.get_param(ns + '/goal_time', 0.0)
89  self.stopped_velocity_tolerance = rospy.get_param(ns + '/stopped_velocity_tolerance', 0.01)
90  self.goal_constraints = []
92  self.min_velocity = rospy.get_param(self.controller_namespace + '/joint_trajectory_action_node/min_velocity', 0.1)
93 
94  for joint in self.joint_names:
95  self.goal_constraints.append(rospy.get_param(ns + '/' + joint + '/goal', -1.0))
96  self.trajectory_constraints.append(rospy.get_param(ns + '/' + joint + '/trajectory', -1.0))
97 
98  # Message containing current state for all controlled joints
99  self.msg = FollowJointTrajectoryFeedback()
100  self.msg.joint_names = self.joint_names
101  self.msg.desired.positions = [0.0] * self.num_joints
102  self.msg.desired.velocities = [0.0] * self.num_joints
103  self.msg.desired.accelerations = [0.0] * self.num_joints
104  self.msg.actual.positions = [0.0] * self.num_joints
105  self.msg.actual.velocities = [0.0] * self.num_joints
106  self.msg.error.positions = [0.0] * self.num_joints
107  self.msg.error.velocities = [0.0] * self.num_joints
108 
109  return True
110 
111  def motor_state_cb(self, data):
112  self.motor_states = data.motor_states
113 
114  def start(self):
115  self.running = True
116 
117  self.command_sub = rospy.Subscriber(self.controller_namespace + '/command', JointTrajectory, self.process_command)
118  self.state_pub = rospy.Publisher(self.controller_namespace + '/state', FollowJointTrajectoryFeedback, queue_size=1)
119  self.action_server = actionlib.SimpleActionServer(self.controller_namespace + '/follow_joint_trajectory',
120  FollowJointTrajectoryAction,
121  execute_cb=self.process_follow_trajectory,
122  auto_start=False)
123  self.action_server.start()
124  Thread(target=self.update_state).start()
125 
126  def stop(self):
127  self.running = False
128 
129  def process_command(self, msg):
130  if self.action_server.is_active(): self.action_server.set_preempted()
131 
132  while self.action_server.is_active():
133  rospy.sleep(0.01)
134 
135  self.process_trajectory(msg)
136 
137  def process_follow_trajectory(self, goal):
138  self.process_trajectory(goal.trajectory)
139 
140  def process_trajectory(self, traj):
141  num_points = len(traj.points)
142 
143  cur_pos = []
144  for i,state in enumerate(self.motor_states):
145  cur_pos.append( ( i+1, SvenzvaDriver.rad_to_raw(self.motor_states[i].position )))
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={}' % (set(self.joint_names)))
154  rospy.logerr(' traj.joint_names={}' % (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  while traj.header.stamp > time:
225  time = rospy.Time.now()
226  rate.sleep()
227 
228  end_time = traj.header.stamp + rospy.Duration(sum(durations))
229  seg_end_times = [rospy.Time.from_sec(trajectory[seg].start_time + durations[seg]) for seg in range(len(trajectory))]
230 
231  rospy.loginfo('Trajectory start time is %.3lf, end time is %.3lf, total duration is %.3lf', time.to_sec(), end_time.to_sec(), sum(durations))
232 
233  self.trajectory = trajectory
234  traj_start_time = rospy.Time.now()
235 
236  for seg in range(len(trajectory)):
237  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()))
238  rospy.logdebug('goal positions are: %s' % str(trajectory[seg].positions))
239 
240  # first point in trajectories calculated by OMPL is current position with duration of 0 seconds, skip it
241  if durations[seg] == 0:
242  rospy.logdebug('skipping segment %d with duration of 0 seconds' % seg)
243  continue
244 
245  vals = []
246  for joint in self.joint_names:
247  j = self.joint_names.index(joint)
248 
249  start_position = self.motor_states[j].position
250  if seg != 0: start_position = trajectory[seg - 1].positions[j]
251 
252  desired_position = trajectory[seg].positions[j]
253  desired_velocity = max(self.min_velocity, abs(desired_position - start_position) / durations[seg])
254 
255  self.msg.desired.positions[j] = desired_position
256  self.msg.desired.velocities[j] = desired_velocity
257 
258  motor_id = self.joint_to_id[joint]
259 
260  pos = SvenzvaDriver.rad_to_raw(desired_position * self.gear_ratios[j])
261  spd = SvenzvaDriver.spd_rad_to_raw(desired_velocity * self.gear_ratios[j])
262  vals.append((motor_id, pos, spd))
263  self.mx_io.set_multi_position_and_speed(vals)
264 
265  while time < seg_end_times[seg]:
266  # check if new trajectory was received, if so abort current trajectory execution
267  # by setting the goal to the current position
268  if self.action_server.is_preempt_requested():
269  msg = 'New trajectory received. Aborting old trajectory.'
270  multi_packet = {}
271 
272  for j, joint in enumerate(self.joint_names):
273  cur_pos = self.motor_states[joint].position
274 
275  motor_id = self.joint_to_id[joint]
276  pos = SvenzvaDriver.rad_to_raw(cur_pos)
277 
278  vals.append((motor_id, pos, 0))
279 
280  self.mx_io.set_multi_position_and_speed(vals)
281  self.action_server.set_preempted(text=msg)
282  rospy.logwarn(msg)
283  return
284 
285  rate.sleep()
286  time = rospy.Time.now()
287 
288  # Verifies trajectory constraints
289  for j, joint in enumerate(self.joint_names):
290  if self.trajectory_constraints[j] > 0 and self.msg.error.positions[j] > self.trajectory_constraints[j]:
291  res = FollowJointTrajectoryResult()
292  res.error_code=FollowJointTrajectoryResult.PATH_TOLERANCE_VIOLATED
293  msg = 'Unsatisfied position constraint for %s, trajectory point %d, %f is larger than %f' % \
294  (joint, seg, self.msg.error.positions[j], self.trajectory_constraints[j])
295  rospy.logwarn(msg)
296  self.action_server.set_aborted(result=res, text=msg)
297  return
298 
299  # let motors roll for specified amount of time
300  rospy.sleep(self.goal_time_constraint)
301 
302  for i, j in enumerate(self.joint_names):
303  rospy.logdebug('desired pos was %f, actual pos is %f, error is %f' % (trajectory[-1].positions[i], self.motor_states[i].position, self.motor_states[i].position - trajectory[-1].positions[i]))
304 
305  # Checks that we have ended inside the goal constraints
306  for (joint, pos_error, pos_constraint) in zip(self.joint_names, self.msg.error.positions, self.goal_constraints):
307  if pos_constraint > 0 and abs(pos_error) > pos_constraint:
308  res = FollowJointTrajectoryResult()
309  res.error_code=FollowJointTrajectoryResult.GOAL_TOLERANCE_VIOLATED
310  msg = 'Aborting because %s joint wound up outside the goal constraints, %f is larger than %f' % \
311  (joint, pos_error, pos_constraint)
312  rospy.logwarn(msg)
313  self.action_server.set_aborted(result=res, text=msg)
314  break
315  else:
316  msg = 'Trajectory execution successfully completed'
317  rospy.loginfo(msg)
318  res = FollowJointTrajectoryResult()
319  res.error_code=FollowJointTrajectoryResult.SUCCESSFUL
320  self.action_server.set_succeeded(result=res, text=msg)
321 
322  def update_state(self):
323  rate = rospy.Rate(self.state_update_rate)
324  while self.running and not rospy.is_shutdown():
325  self.msg.header.stamp = rospy.Time.now()
326 
327  # Publish current joint state
328  for i, joint in enumerate(self.joint_names):
329  state = self.motor_states[i]
330  self.msg.actual.positions[i] = state.position
331  self.msg.actual.velocities[i] = abs(state.speed)
332  self.msg.error.positions[i] = self.msg.actual.positions[i] - self.msg.desired.positions[i]
333  self.msg.error.velocities[i] = self.msg.actual.velocities[i] - self.msg.desired.velocities[i]
334 
335  self.state_pub.publish(self.msg)
336  rate.sleep()


svenzva_drivers
Author(s): Max Svetlik
autogenerated on Wed Oct 28 2020 03:31:27