velocity_smoother.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *   http://www.apache.org/licenses/LICENSE-2.0
00009 
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*****************************************************************************
00018  ** Includes
00019  *****************************************************************************/
00020 
00021 #include <ros/ros.h>
00022 
00023 #include <dynamic_reconfigure/server.h>
00024 #include <cob_base_velocity_smoother/velocity_smoother.h>
00025 
00026 #include <boost/thread.hpp>
00027 
00028 /*****************************************************************************
00029  ** Preprocessing
00030  *****************************************************************************/
00031 
00032 #define PERIOD_RECORD_SIZE    5
00033 #define ZERO_VEL_COMMAND      geometry_msgs::Twist();
00034 #define IS_ZERO_VEOCITY(a)   ((a.linear.x == 0.0) && (a.linear.y == 0.0) && (a.angular.z == 0.0))
00035 
00036 /*****************************************************************************
00037 ** Namespaces
00038 *****************************************************************************/
00039 
00040 namespace cob_base_velocity_smoother {
00041 
00042 /*********************
00043 ** Implementation
00044 **********************/
00045 
00046 VelocitySmoother::VelocitySmoother(const std::string &name)
00047 : name(name)
00048 , shutdown_req(false)
00049 , input_active(false)
00050 , pr_next(0)
00051 , dynamic_reconfigure_server(NULL)
00052 {
00053 }
00054 
00055 void VelocitySmoother::reconfigCB(cob_base_velocity_smoother::paramsConfig &config, uint32_t level)
00056 {
00057   ROS_INFO("Reconfigure request : %f %f %f %f %f %f %f %f",
00058            config.speed_lim_vx, config.speed_lim_vy, config.speed_lim_w, config.accel_lim_vx, config.accel_lim_vy, config.accel_lim_w, config.decel_factor, config.decel_factor_safe);
00059 
00060   speed_lim_vx  = config.speed_lim_vx;
00061   speed_lim_vy  = config.speed_lim_vy;
00062   speed_lim_w  = config.speed_lim_w;
00063   accel_lim_vx  = config.accel_lim_vx;
00064   accel_lim_vy  = config.accel_lim_vy;
00065   accel_lim_w  = config.accel_lim_w;
00066   decel_factor = config.decel_factor;
00067   decel_factor_safe = config.decel_factor_safe;
00068   decel_lim_vx  = decel_factor*accel_lim_vx;
00069   decel_lim_vy  = decel_factor*accel_lim_vy;
00070   decel_lim_w  = decel_factor*accel_lim_w;
00071   decel_lim_vx_safe = decel_factor_safe*accel_lim_vx;
00072   decel_lim_vy_safe = decel_factor_safe*accel_lim_vy;
00073   decel_lim_w_safe = decel_factor_safe*accel_lim_w;
00074 }
00075 
00076 void VelocitySmoother::velocityCB(const geometry_msgs::Twist::ConstPtr& msg)
00077 {
00078   // Estimate commands frequency; we do continuously as it can be very different depending on the
00079   // publisher type, and we don't want to impose extra constraints to keep this package flexible
00080   if (period_record.size() < PERIOD_RECORD_SIZE)
00081   {
00082     period_record.push_back((ros::Time::now() - last_cb_time).toSec());
00083   }
00084   else
00085   {
00086     period_record[pr_next] = (ros::Time::now() - last_cb_time).toSec();
00087   }
00088 
00089   pr_next++;
00090   pr_next %= period_record.size();
00091   last_cb_time = ros::Time::now();
00092 
00093   if (period_record.size() <= PERIOD_RECORD_SIZE/2)
00094   {
00095     // wait until we have some values; make a reasonable assumption (10 Hz) meanwhile
00096     cb_avg_time = 0.1;
00097   }
00098   else
00099   {
00100     // enough; recalculate with the latest input
00101     cb_avg_time = median(period_record);
00102   }
00103 
00104   input_active = true;
00105 
00106   // Bound speed with the maximum values
00107   target_vel.linear.x  =
00108       msg->linear.x  > 0.0 ? std::min(msg->linear.x,  speed_lim_vx) : std::max(msg->linear.x,  -speed_lim_vx);
00109   target_vel.linear.y  =
00110       msg->linear.y  > 0.0 ? std::min(msg->linear.y,  speed_lim_vy) : std::max(msg->linear.y,  -speed_lim_vy);
00111   target_vel.angular.z =
00112       msg->angular.z > 0.0 ? std::min(msg->angular.z, speed_lim_w) : std::max(msg->angular.z, -speed_lim_w);
00113 }
00114 
00115 void VelocitySmoother::odometryCB(const nav_msgs::Odometry::ConstPtr& msg)
00116 {
00117   if (robot_feedback == ODOMETRY)
00118     current_vel = msg->twist.twist;
00119 
00120   // ignore otherwise
00121 }
00122 
00123 void VelocitySmoother::robotVelCB(const geometry_msgs::Twist::ConstPtr& msg)
00124 {
00125   if (robot_feedback == COMMANDS)
00126     current_vel = *msg;
00127 
00128   // ignore otherwise
00129 }
00130 
00131 void VelocitySmoother::spin()
00132 {
00133   double period = 1.0/frequency;
00134   ros::Rate spin_rate(frequency);
00135 
00136   double decel_vx;
00137   double decel_vy;
00138   double decel_w;
00139 
00140   while (! shutdown_req && ros::ok())
00141   {
00142     if ((input_active == true) && (cb_avg_time > 0.0) &&
00143         ((ros::Time::now() - last_cb_time).toSec() > std::min(3.0*cb_avg_time, 0.5)))
00144     {
00145       // Velocity input no active anymore; normally last command is a zero-velocity one, but reassure
00146       // this, just in case something went wrong with our input, or he just forgot good manners...
00147       // Issue #2, extra check in case cb_avg_time is very big, for example with several atomic commands
00148       // The cb_avg_time > 0 check is required to deal with low-rate simulated time, that can make that
00149       // several messages arrive with the same time and so lead to a zero median
00150       input_active = false;
00151       if (IS_ZERO_VEOCITY(target_vel) == false)
00152       {
00153         ROS_WARN_STREAM("Velocity Smoother : input got inactive leaving us a non-zero target velocity ("
00154               << target_vel.linear.x << ", " << target_vel.linear.y << ", " << target_vel.angular.z << "), zeroing...[" << name << "]");
00155         target_vel = ZERO_VEL_COMMAND;
00156       }
00157     }
00158 
00159     if(input_active)
00160     {
00161       decel_vx = decel_lim_vx;
00162       decel_vy = decel_lim_vy;
00163       decel_w = decel_lim_w;
00164     }
00165     else
00166     {
00167       //increase decel factor because this is a safty case, no more commands means we should stop as fast as it is safe
00168       decel_vx = decel_lim_vx_safe;
00169       decel_vy = decel_lim_vy_safe;
00170       decel_w = decel_lim_w_safe;
00171     }
00172 
00173     if ((robot_feedback != NONE) && (input_active == true) && (cb_avg_time > 0.0) &&
00174         (((ros::Time::now() - last_cb_time).toSec() > 5.0*cb_avg_time)     || // 5 missing msgs
00175           (std::abs(current_vel.linear.x  - last_cmd_vel.linear.x)  > 0.2) ||
00176           (std::abs(current_vel.linear.y  - last_cmd_vel.linear.y)  > 0.2) ||
00177           (std::abs(current_vel.angular.z - last_cmd_vel.angular.z) > 2.0)))
00178     {
00179       // If the publisher has been inactive for a while, or if our current commanding differs a lot
00180       // from robot velocity feedback, we cannot trust the former; relay on robot's feedback instead
00181       // This can happen mainly due to preemption of current controller on velocity multiplexer.
00182       // TODO: current command/feedback difference thresholds are 진짜 arbitrary; they should somehow
00183       // be proportional to max v and w...
00184       // The one for angular velocity is very big because is it's less necessary (for example the
00185       // reactive controller will never make the robot spin) and because the gyro has a 15 ms delay
00186       ROS_WARN("Using robot velocity feedback (%s) instead of last command: %f, %f, %f, %f",
00187                 robot_feedback == ODOMETRY ? "odometry" : "end commands",
00188                (ros::Time::now()      - last_cb_time).toSec(),
00189                 current_vel.linear.x  - last_cmd_vel.linear.x,
00190                 current_vel.linear.y  - last_cmd_vel.linear.y,
00191                 current_vel.angular.z - last_cmd_vel.angular.z);
00192       last_cmd_vel = current_vel;
00193     }
00194 
00195     geometry_msgs::TwistPtr cmd_vel;
00196 
00197     if ((target_vel.linear.x  != last_cmd_vel.linear.x) ||
00198         (target_vel.linear.y  != last_cmd_vel.linear.y) ||
00199         (target_vel.angular.z != last_cmd_vel.angular.z))
00200     {
00201       // Try to reach target velocity ensuring that we don't exceed the acceleration limits
00202       cmd_vel.reset(new geometry_msgs::Twist(target_vel));
00203 
00204       double vx_inc, vy_inc, w_inc, max_vx_inc, max_vy_inc, max_w_inc;
00205 
00206       vx_inc = target_vel.linear.x - last_cmd_vel.linear.x;
00207       if ((robot_feedback == ODOMETRY) && (current_vel.linear.x*target_vel.linear.x < 0.0))
00208       {
00209         // countermarch (on robots with significant inertia; requires odometry feedback to be detected)
00210         max_vx_inc = decel_vx*period;
00211       }
00212       else
00213       {
00214         max_vx_inc = ((vx_inc*target_vel.linear.x > 0.0)?accel_lim_vx:decel_vx)*period;
00215       }
00216 
00217       vy_inc = target_vel.linear.y - last_cmd_vel.linear.y;
00218       if ((robot_feedback == ODOMETRY) && (current_vel.linear.y*target_vel.linear.y < 0.0))
00219       {
00220         // countermarch (on robots with significant inertia; requires odometry feedback to be detected)
00221         max_vy_inc = decel_vy*period;
00222       }
00223       else
00224       {
00225         max_vy_inc = ((vy_inc*target_vel.linear.x > 0.0)?accel_lim_vy:decel_vy)*period;
00226       }
00227 
00228       w_inc = target_vel.angular.z - last_cmd_vel.angular.z;
00229       if ((robot_feedback == ODOMETRY) && (current_vel.angular.z*target_vel.angular.z < 0.0))
00230       {
00231         // countermarch (on robots with significant inertia; requires odometry feedback to be detected)
00232         max_w_inc = decel_w*period;
00233       }
00234       else
00235       {
00236         max_w_inc = ((w_inc*target_vel.angular.z > 0.0)?accel_lim_w:decel_w)*period;
00237       }
00238 
00239 /*
00240       // Calculate and normalise vectors A (desired velocity increment) and B (maximum velocity increment),
00241       // where v acts as coordinate x and w as coordinate y; the sign of the angle from A to B determines
00242       // which velocity (v or w) must be overconstrained to keep the direction provided as command
00243       double MA = sqrt(    vx_inc *     vx_inc +     w_inc *     w_inc);
00244       double MB = sqrt(max_vx_inc * max_vx_inc + max_w_inc * max_w_inc);
00245 
00246       double Av = std::abs(vx_inc) / MA;
00247       double Aw = std::abs(w_inc) / MA;
00248       double Bv = max_vx_inc / MB;
00249       double Bw = max_w_inc / MB;
00250       double theta = atan2(Bw, Bv) - atan2(Aw, Av);
00251 
00252       if (theta < 0)
00253       {
00254         // overconstrain linear velocity
00255         max_vx_inc = (max_w_inc*std::abs(vx_inc))/std::abs(w_inc);
00256       }
00257       else
00258       {
00259         // overconstrain angular velocity
00260         max_w_inc = (max_vx_inc*std::abs(w_inc))/std::abs(vx_inc);
00261       }
00262 */
00263       if (std::abs(vx_inc) > max_vx_inc)
00264       {
00265         // we must limit linear velocity
00266         cmd_vel->linear.x  = last_cmd_vel.linear.x  + sign(vx_inc)*max_vx_inc;
00267       }
00268 
00269       if (std::abs(vy_inc) > max_vy_inc)
00270       {
00271         // we must limit linear velocity
00272         cmd_vel->linear.y  = last_cmd_vel.linear.y  + sign(vy_inc)*max_vy_inc;
00273       }
00274 
00275       if (std::abs(w_inc) > max_w_inc)
00276       {
00277         // we must limit angular velocity
00278         cmd_vel->angular.z = last_cmd_vel.angular.z + sign(w_inc)*max_w_inc;
00279       }
00280 
00281       smooth_vel_pub.publish(cmd_vel);
00282       last_cmd_vel = *cmd_vel;
00283     }
00284     else if (input_active == true)
00285     {
00286       // We already reached target velocity; just keep resending last command while input is active
00287       cmd_vel.reset(new geometry_msgs::Twist(last_cmd_vel));
00288       smooth_vel_pub.publish(cmd_vel);
00289     }
00290 
00291     spin_rate.sleep();
00292   }
00293 }
00294 
00300 bool VelocitySmoother::init(ros::NodeHandle& nh)
00301 {
00302   // Dynamic Reconfigure
00303   dynamic_reconfigure_callback = boost::bind(&VelocitySmoother::reconfigCB, this, _1, _2);
00304 
00305   dynamic_reconfigure_server = new dynamic_reconfigure::Server<cob_base_velocity_smoother::paramsConfig>(nh);
00306   dynamic_reconfigure_server->setCallback(dynamic_reconfigure_callback);
00307 
00308   // Optional parameters
00309   int feedback;
00310   nh.param("frequency",      frequency,     20.0);
00311   nh.param("decel_factor",   decel_factor,   1.0);
00312   nh.param("decel_factor_safe",   decel_factor_safe,   1.0);
00313   nh.param("robot_feedback", feedback, (int)NONE);
00314 
00315   if ((int(feedback) < NONE) || (int(feedback) > COMMANDS))
00316   {
00317     ROS_WARN("Invalid robot feedback type (%d). Valid options are 0 (NONE, default), 1 (ODOMETRY) and 2 (COMMANDS)",
00318              feedback);
00319     feedback = NONE;
00320   }
00321 
00322   robot_feedback = static_cast<RobotFeedbackType>(feedback);
00323 
00324   // Mandatory parameters
00325   if ((nh.getParam("speed_lim_vx", speed_lim_vx) == false) ||
00326       (nh.getParam("speed_lim_vy", speed_lim_vy) == false) ||
00327       (nh.getParam("speed_lim_w", speed_lim_w) == false))
00328   {
00329     ROS_ERROR("Missing velocity limit parameter(s)");
00330     return false;
00331   }
00332 
00333   if ((nh.getParam("accel_lim_vx", accel_lim_vx) == false) ||
00334       (nh.getParam("accel_lim_vy", accel_lim_vy) == false) ||
00335       (nh.getParam("accel_lim_w", accel_lim_w) == false))
00336   {
00337     ROS_ERROR("Missing acceleration limit parameter(s)");
00338     return false;
00339   }
00340 
00341   // Deceleration can be more aggressive, if necessary
00342   decel_lim_vx = decel_factor*accel_lim_vx;
00343   decel_lim_vy = decel_factor*accel_lim_vy;
00344   decel_lim_w = decel_factor*accel_lim_w;
00345   // In safety cases (no topic command anymore), deceleration should be very aggressive
00346   decel_lim_vx_safe = decel_factor_safe*accel_lim_vx;
00347   decel_lim_vy_safe = decel_factor_safe*accel_lim_vy;
00348   decel_lim_w_safe = decel_factor_safe*accel_lim_w;
00349 
00350   // Publishers and subscribers
00351   odometry_sub    = nh.subscribe("odometry",      1, &VelocitySmoother::odometryCB, this);
00352   current_vel_sub = nh.subscribe("robot_cmd_vel", 1, &VelocitySmoother::robotVelCB, this);
00353   raw_in_vel_sub  = nh.subscribe("raw_cmd_vel",   1, &VelocitySmoother::velocityCB, this);
00354   smooth_vel_pub  = nh.advertise <geometry_msgs::Twist> ("smooth_cmd_vel", 1);
00355 
00356   return true;
00357 }
00358 }


cob_base_velocity_smoother
Author(s): Florian Mirus , Benjamin Maidel
autogenerated on Thu Jun 6 2019 21:18:55