diff_drive_base.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2014-2017, Fetch Robotics Inc.
5  * Copyright (c) 2013, Unbounded Robotics Inc.
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 Unbounded Robotics 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 
36 // Author: Michael Ferguson
37 
38 #include <angles/angles.h>
41 
43 
44 namespace robot_controllers
45 {
46 
47 DiffDriveBaseController::DiffDriveBaseController() :
48  initialized_(false),
49  safety_scaling_(1.0)
50 {
51  theta_ = 0.0;
52 
53  odom_.pose.pose.orientation.z = 0.0;
54  odom_.pose.pose.orientation.w = 1.0;
55 
56  last_sent_x_ = desired_x_ = 0.0;
57  last_sent_r_ = desired_r_ = 0.0;
58 
61 }
62 
64 {
65  // We absolutely need access to the controller manager
66  if (!manager)
67  {
68  ROS_ERROR_NAMED("BaseController", "No controller manager available.");
69  initialized_ = false;
70  return -1;
71  }
72 
73  Controller::init(nh, manager);
74  manager_ = manager;
75 
76  // Initialize joints
77  std::string l_name, r_name;
78  nh.param<std::string>("l_wheel_joint", l_name, "l_wheel_joint");
79  nh.param<std::string>("r_wheel_joint", r_name, "r_wheel_joint");
80  left_ = manager_->getJointHandle(l_name);
81  right_ = manager_->getJointHandle(r_name);
82  if (left_ == NULL || right_ == NULL)
83  {
84  ROS_ERROR_NAMED("BaseController", "Cannot get wheel joints.");
85  initialized_ = false;
86  return -1;
87  }
88  left_last_position_ = left_->getPosition();
89  right_last_position_ = right_->getPosition();
91 
92  // Get base parameters
93  nh.param<double>("track_width", track_width_, 0.37476);
94  nh.param<double>("radians_per_meter", radians_per_meter_, 16.5289);
95 
96  // If using an external correction (such as robot_pose_ekf or graft)
97  // we should not publish the TF frame from base->odom
98  nh.param<bool>("publish_tf", publish_tf_, true);
99 
100  // The pose in the odometry message is specified in terms of the odometry frame
101  nh.param<std::string>("odometry_frame", odom_.header.frame_id, "odom");
102 
103  // The twist in the odometry message is specified in the coordinate frame of the base
104  nh.param<std::string>("base_frame", odom_.child_frame_id, "base_link");
105 
106  // Get various thresholds below which we supress noise
107  nh.param<double>("wheel_rotating_threshold", wheel_rotating_threshold_, 0.001);
108  nh.param<double>("rotating_threshold", rotating_threshold_, 0.05);
109  nh.param<double>("moving_threshold", moving_threshold_, 0.05);
110 
111  double t;
112  nh.param<double>("timeout", t, 0.25);
113  timeout_ = ros::Duration(t);
114 
115  // Get limits of base controller
116  nh.param<double>("max_velocity_x", max_velocity_x_, 1.0);
117  nh.param<double>("max_velocity_r", max_velocity_r_, 4.5);
118  nh.param<double>("max_acceleration_x", max_acceleration_x_, 0.75);
119  nh.param<double>("max_acceleration_r", max_acceleration_r_, 3.0);
120 
121  // Subscribe to base commands
122  cmd_sub_ = nh.subscribe<geometry_msgs::Twist>("command", 1,
123  boost::bind(&DiffDriveBaseController::command, this, _1));
124 
125  // Publish odometry & tf
126  ros::NodeHandle n;
127  odom_pub_ = n.advertise<nav_msgs::Odometry>("odom", 10);
128  if (publish_tf_)
130 
131  // Publish timer
132  double publish_frequency;
133  nh.param<double>("publish_frequency", publish_frequency, 100.0);
134  odom_timer_ = n.createTimer(ros::Duration(1/publish_frequency),
136  this);
137 
138  // Should we use the laser for safety limiting base velocity?
139  nh.param<double>("laser_safety_dist", safety_scaling_distance_, 0.0);
140  nh.param<double>("robot_safety_width", robot_width_, 0.7);
141  if (safety_scaling_distance_ > 0.0)
142  {
143  scan_sub_ = n.subscribe<sensor_msgs::LaserScan>("base_scan", 1,
144  boost::bind(&DiffDriveBaseController::scanCallback, this, _1));
145  }
146 
147  initialized_ = true;
148 
149  // Should we autostart?
150  bool autostart;
151  nh.param("autostart", autostart, false);
152  if (autostart)
153  manager->requestStart(getName());
154 
155  return 0;
156 }
157 
158 void DiffDriveBaseController::command(const geometry_msgs::TwistConstPtr& msg)
159 {
160  if (!initialized_)
161  {
162  ROS_ERROR_NAMED("BaseController", "Unable to accept command, not initialized.");
163  return;
164  }
165 
166  if (std::isfinite(msg->linear.x) && std::isfinite(msg->angular.z))
167  {
168  boost::mutex::scoped_lock lock(command_mutex_);
170  desired_x_ = msg->linear.x;
171  desired_r_ = msg->angular.z;
172  }
173  else
174  {
175  ROS_ERROR_NAMED("BaseController", "Commanded velocities not finite!");
176  return;
177  }
178 
180 }
181 
183 {
184  if (!initialized_)
185  {
186  ROS_ERROR_NAMED("BaseController", "Unable to start, not initialized.");
187  return false;
188  }
189 
190  return true;
191 }
192 
194 {
195  // If we have timed out, certainly stop
197  return true;
198 
199  // If not moving, stop
200  if (last_sent_x_ == 0.0 && last_sent_r_ == 0.0)
201  return true;
202 
203  // If forced, stop
204  if (force)
205  return true;
206 
207  return false;
208 }
209 
211 {
212  // Reset command
214  return true;
215 }
216 
218 {
219  if (!initialized_)
220  return; // should never really hit this
221 
222  // See if we have timed out and need to stop
223  if (now - last_command_ >= timeout_)
224  {
225  ROS_DEBUG_THROTTLE_NAMED(5, "BaseController", "Command timed out.");
226  boost::mutex::scoped_lock lock(command_mutex_);
227  desired_x_ = desired_r_ = 0.0;
228  }
229 
230  // Make sure laser has not timed out
231  if ((safety_scaling_distance_ > 0.0) &&
233  {
234  safety_scaling_ = 0.1;
235  }
236 
237  // Do velocity acceleration/limiting
238  double x, r;
239  {
240  boost::mutex::scoped_lock lock(command_mutex_);
241  // Limit linear velocity based on obstacles
242  x = std::max(-max_velocity_x_ * safety_scaling_, std::min(desired_x_, max_velocity_x_ * safety_scaling_));
243  // Compute how much we actually scaled the linear velocity
244  double actual_scaling = 1.0;
245  if (desired_x_ != 0.0)
246  actual_scaling = x/desired_x_;
247  // Limit angular velocity
248  // Scale same amount as linear velocity so that robot still follows the same "curvature"
249  r = std::max(-max_velocity_r_, std::min(actual_scaling * desired_r_, max_velocity_r_));
250  }
251  if (x > last_sent_x_)
252  {
253  last_sent_x_ += max_acceleration_x_ * (now - last_update_).toSec();
254  if (last_sent_x_ > x)
255  last_sent_x_ = x;
256  }
257  else
258  {
259  last_sent_x_ -= max_acceleration_x_ * (now - last_update_).toSec();
260  if (last_sent_x_ < x)
261  last_sent_x_ = x;
262  }
263  if (r > last_sent_r_)
264  {
265  last_sent_r_ += max_acceleration_r_ * (now - last_update_).toSec();
266  if (last_sent_r_ > r)
267  last_sent_r_ = r;
268  }
269  else
270  {
271  last_sent_r_ -= max_acceleration_r_ * (now - last_update_).toSec();
272  if (last_sent_r_ < r)
273  last_sent_r_ = r;
274  }
275 
276  double dx = 0.0;
277  double dr = 0.0;
278 
279  double left_pos = left_->getPosition();
280  double right_pos = right_->getPosition();
283  double left_vel = static_cast<double>(left_->getVelocity())/radians_per_meter_;
284  double right_vel = static_cast<double>(right_->getVelocity())/radians_per_meter_;
285 
286  // Threshold the odometry to avoid noise (especially in simulation)
287  if (fabs(left_dx) > wheel_rotating_threshold_ ||
288  fabs(right_dx) > wheel_rotating_threshold_ ||
289  last_sent_x_ != 0.0 ||
290  last_sent_r_ != 0.0)
291  {
292  // Above threshold, update last position
293  left_last_position_ = left_pos;
294  right_last_position_ = right_pos;
295  }
296  else
297  {
298  // Below threshold, zero delta/velocities
299  left_dx = right_dx = 0.0;
300  left_vel = right_vel = 0.0;
301  }
302 
303  // Calculate forward and angular differences
304  double d = (left_dx+right_dx)/2.0;
305  double th = (right_dx-left_dx)/track_width_;
306 
307  // Calculate forward and angular velocities
308  dx = (left_vel + right_vel)/2.0;
309  dr = (right_vel - left_vel)/track_width_;
310 
311  // Actually set command
312  if (fabs(dx) > moving_threshold_ ||
313  fabs(dr) > rotating_threshold_ ||
314  last_sent_x_ != 0.0 ||
315  last_sent_r_ != 0.0)
316  {
319  }
320 
321  // Lock mutex before updating
322  boost::mutex::scoped_lock lock(odom_mutex_);
323 
324  // Update stored odometry pose...
325  theta_ += th/2.0;
326  odom_.pose.pose.position.x += d*cos(theta_);
327  odom_.pose.pose.position.y += d*sin(theta_);
328  theta_ += th/2.0;
329  odom_.pose.pose.orientation.z = sin(theta_/2.0);
330  odom_.pose.pose.orientation.w = cos(theta_/2.0);
331  // ...and twist
332  odom_.twist.twist.linear.x = dx;
333  odom_.twist.twist.angular.z = dr;
334 
335  last_update_ = now;
336 }
337 
339 {
340  std::vector<std::string> names;
341  if (left_)
342  names.push_back(left_->getName());
343  if (right_)
344  names.push_back(right_->getName());
345  return names;
346 }
347 
349 {
350  // Claimed == Commanded
351  return getCommandedNames();
352 }
353 
355 {
356  // Copy message under lock of mutex
357  nav_msgs::Odometry msg;
358  {
359  boost::mutex::scoped_lock lock(odom_mutex_);
360  msg = odom_;
361  }
362 
363  // Publish or perish
364  msg.header.stamp = ros::Time::now();
365  odom_pub_.publish(msg);
366 
367  if (publish_tf_)
368  {
369  tf::Transform transform;
370  transform.setOrigin(tf::Vector3(msg.pose.pose.position.x, msg.pose.pose.position.y, 0.0));
371  transform.setRotation(tf::Quaternion(msg.pose.pose.orientation.x,
372  msg.pose.pose.orientation.y,
373  msg.pose.pose.orientation.z,
374  msg.pose.pose.orientation.w) );
375  /*
376  * REP105 (http://ros.org/reps/rep-0105.html)
377  * says: map -> odom -> base_link
378  */
379  broadcaster_->sendTransform(tf::StampedTransform(transform, msg.header.stamp, msg.header.frame_id, msg.child_frame_id));
380  }
381 }
382 
384  const sensor_msgs::LaserScanConstPtr& scan)
385 {
386  double angle = scan->angle_min;
387  double min_dist = safety_scaling_distance_;
388 
389  for (size_t i = 0; i < scan->ranges.size(); ++i, angle += scan->angle_increment)
390  {
391  if (std::isfinite(scan->ranges[i]) &&
392  scan->ranges[i] > scan->range_min &&
393  scan->ranges[i] < min_dist)
394  {
395  // Only test points in the forward 180 degrees
396  if (angle < -1.5 || angle > 1.5)
397  continue;
398 
399  // Check if point is inside the width of the robot
400  double py = sin(angle) * scan->ranges[i];
401  if (fabs(py) < (robot_width_/2.0))
402  min_dist = scan->ranges[i];
403  }
404  }
405 
406  boost::mutex::scoped_lock lock(command_mutex_);
407  safety_scaling_ = std::max(0.1, min_dist / safety_scaling_distance_);
409 }
410 
411 void DiffDriveBaseController::setCommand(float left, float right)
412 {
413  // Convert meters/sec into radians/sec
414  left_->setVelocity(left * radians_per_meter_, 0.0);
415  right_->setVelocity(right * radians_per_meter_, 0.0);
416 }
417 
418 } // namespace robot_controllers
d
TFSIMD_FORCE_INLINE void setRotation(const Quaternion &q)
double rotating_threshold_
Threshold for wheel velocity to be "moving".
virtual int init(ros::NodeHandle &nh, ControllerManager *manager)
Initialize the controller and any required data structures.
void publishCallback(const ros::TimerEvent &event)
void setCommand(float left, float right)
void publish(const boost::shared_ptr< M > &message) const
void command(const geometry_msgs::TwistConstPtr &msg)
Command callback from either a ROS topic, or a higher controller.
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
virtual bool start()
Attempt to start the controller. This should be called only by the ControllerManager instance...
#define ROS_DEBUG_THROTTLE_NAMED(rate, name,...)
virtual int requestStart(const std::string &name)
void scanCallback(const sensor_msgs::LaserScanConstPtr &scan)
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
TFSIMD_FORCE_INLINE const tfScalar & x() const
double moving_threshold_
Threshold for dr to be considered "moving".
double max_velocity_x_
Threshold for dx to be considered "moving".
virtual bool stop(bool force)
Attempt to stop the controller. This should be called only by the ControllerManager instance...
virtual bool reset()
Cleanly reset the controller to it&#39;s initial state. Some controllers may choose to stop themselves...
virtual int init(ros::NodeHandle &nh, ControllerManager *manager)
virtual std::vector< std::string > getClaimedNames()
Get the names of joints/controllers which this controller exclusively claims.
boost::shared_ptr< tf::TransformBroadcaster > broadcaster_
#define ROS_ERROR_NAMED(name,...)
static Time now()
virtual std::vector< std::string > getCommandedNames()
Get the names of joints/controllers which this controller commands.
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
JointHandlePtr getJointHandle(const std::string &name)
ROS-aware controller to manage a differential drive mobile base. This subcribes to cmd_vel topic...
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
TFSIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)
def shortest_angular_distance(from_angle, to_angle)
virtual void update(const ros::Time &now, const ros::Duration &dt)
This is the update loop for the controller.


robot_controllers
Author(s): Michael Ferguson
autogenerated on Sun Sep 27 2020 03:22:39