diff --git a/src/systems/diff_drive/CMakeLists.txt b/src/systems/diff_drive/CMakeLists.txt index 0a415da6d4..7c7da3ec05 100644 --- a/src/systems/diff_drive/CMakeLists.txt +++ b/src/systems/diff_drive/CMakeLists.txt @@ -1,7 +1,6 @@ gz_add_system(diff-drive SOURCES DiffDrive.cc - SpeedLimiter.cc PUBLIC_LINK_LIBS ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER} ) diff --git a/src/systems/diff_drive/DiffDrive.cc b/src/systems/diff_drive/DiffDrive.cc index e8a49f4356..eb356bd491 100644 --- a/src/systems/diff_drive/DiffDrive.cc +++ b/src/systems/diff_drive/DiffDrive.cc @@ -27,6 +27,7 @@ #include <ignition/common/Profiler.hh> #include <ignition/math/DiffDriveOdometry.hh> #include <ignition/math/Quaternion.hh> +#include <ignition/math/SpeedLimiter.hh> #include <ignition/plugin/Register.hh> #include <ignition/transport/Node.hh> @@ -36,8 +37,6 @@ #include "ignition/gazebo/Link.hh" #include "ignition/gazebo/Model.hh" -#include "SpeedLimiter.hh" - using namespace ignition; using namespace gazebo; using namespace systems; @@ -123,10 +122,10 @@ class ignition::gazebo::systems::DiffDrivePrivate public: transport::Node::Publisher tfPub; /// \brief Linear velocity limiter. - public: std::unique_ptr<SpeedLimiter> limiterLin; + public: std::unique_ptr<ignition::math::SpeedLimiter> limiterLin; /// \brief Angular velocity limiter. - public: std::unique_ptr<SpeedLimiter> limiterAng; + public: std::unique_ptr<ignition::math::SpeedLimiter> limiterAng; /// \brief Previous control command. public: Commands last0Cmd; @@ -197,56 +196,48 @@ void DiffDrive::Configure(const Entity &_entity, this->dataPtr->wheelRadius = _sdf->Get<double>("wheel_radius", this->dataPtr->wheelRadius).first; - // Parse speed limiter parameters. - bool hasVelocityLimits = false; - bool hasAccelerationLimits = false; - bool hasJerkLimits = false; - double minVel = std::numeric_limits<double>::lowest(); - double maxVel = std::numeric_limits<double>::max(); - double minAccel = std::numeric_limits<double>::lowest(); - double maxAccel = std::numeric_limits<double>::max(); - double minJerk = std::numeric_limits<double>::lowest(); - double maxJerk = std::numeric_limits<double>::max(); + // Instantiate the speed limiters. + this->dataPtr->limiterLin = std::make_unique<ignition::math::SpeedLimiter>(); + this->dataPtr->limiterAng = std::make_unique<ignition::math::SpeedLimiter>(); + // Parse speed limiter parameters. if (_sdf->HasElement("min_velocity")) { - minVel = _sdf->Get<double>("min_velocity"); - hasVelocityLimits = true; + const double minVel = _sdf->Get<double>("min_velocity"); + this->dataPtr->limiterLin->SetMinVelocity(minVel); + this->dataPtr->limiterAng->SetMinVelocity(minVel); } if (_sdf->HasElement("max_velocity")) { - maxVel = _sdf->Get<double>("max_velocity"); - hasVelocityLimits = true; + const double maxVel = _sdf->Get<double>("max_velocity"); + this->dataPtr->limiterLin->SetMaxVelocity(maxVel); + this->dataPtr->limiterAng->SetMaxVelocity(maxVel); } if (_sdf->HasElement("min_acceleration")) { - minAccel = _sdf->Get<double>("min_acceleration"); - hasAccelerationLimits = true; + const double minAccel = _sdf->Get<double>("min_acceleration"); + this->dataPtr->limiterLin->SetMinAcceleration(minAccel); + this->dataPtr->limiterAng->SetMinAcceleration(minAccel); } if (_sdf->HasElement("max_acceleration")) { - maxAccel = _sdf->Get<double>("max_acceleration"); - hasAccelerationLimits = true; + const double maxAccel = _sdf->Get<double>("max_acceleration"); + this->dataPtr->limiterLin->SetMaxAcceleration(maxAccel); + this->dataPtr->limiterAng->SetMaxAcceleration(maxAccel); } if (_sdf->HasElement("min_jerk")) { - minJerk = _sdf->Get<double>("min_jerk"); - hasJerkLimits = true; + const double minJerk = _sdf->Get<double>("min_jerk"); + this->dataPtr->limiterLin->SetMinJerk(minJerk); + this->dataPtr->limiterAng->SetMinJerk(minJerk); } if (_sdf->HasElement("max_jerk")) { - maxJerk = _sdf->Get<double>("max_jerk"); - hasJerkLimits = true; + const double maxJerk = _sdf->Get<double>("max_jerk"); + this->dataPtr->limiterLin->SetMaxJerk(maxJerk); + this->dataPtr->limiterAng->SetMaxJerk(maxJerk); } - // Instantiate the speed limiters. - this->dataPtr->limiterLin = std::make_unique<SpeedLimiter>( - hasVelocityLimits, hasAccelerationLimits, hasJerkLimits, - minVel, maxVel, minAccel, maxAccel, minJerk, maxJerk); - - this->dataPtr->limiterAng = std::make_unique<SpeedLimiter>( - hasVelocityLimits, hasAccelerationLimits, hasJerkLimits, - minVel, maxVel, minAccel, maxAccel, minJerk, maxJerk); double odomFreq = _sdf->Get<double>("odom_publish_frequency", 50).first; if (odomFreq > 0) @@ -502,11 +493,11 @@ void DiffDrivePrivate::UpdateVelocity(const ignition::gazebo::UpdateInfo &_info, angVel = this->targetVel.angular().z(); } - const double dt = std::chrono::duration<double>(_info.dt).count(); - // Limit the target velocity if needed. - this->limiterLin->Limit(linVel, this->last0Cmd.lin, this->last1Cmd.lin, dt); - this->limiterAng->Limit(angVel, this->last0Cmd.ang, this->last1Cmd.ang, dt); + this->limiterLin->Limit( + linVel, this->last0Cmd.lin, this->last1Cmd.lin, _info.dt); + this->limiterAng->Limit( + angVel, this->last0Cmd.ang, this->last1Cmd.ang, _info.dt); // Update history of commands. this->last1Cmd = last0Cmd; diff --git a/src/systems/diff_drive/SpeedLimiter.cc b/src/systems/diff_drive/SpeedLimiter.cc deleted file mode 100644 index 59b50f54b9..0000000000 --- a/src/systems/diff_drive/SpeedLimiter.cc +++ /dev/null @@ -1,200 +0,0 @@ -/********************************************************************* - * Software License Agreement (BSD License) - * - * Copyright (c) 2013, PAL Robotics, S.L. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * * Neither the name of the PAL Robotics nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *********************************************************************/ - -/* - * Author: Enrique Fernández - * Modified: Carlos Agüero - */ - -#include <ignition/math/Helpers.hh> - -#include "SpeedLimiter.hh" - -using namespace ignition; -using namespace gazebo; -using namespace systems; - -/// \brief Private SpeedLimiter data class. -class ignition::gazebo::systems::SpeedLimiterPrivate -{ - /// \brief Class constructor. - public: SpeedLimiterPrivate(bool _hasVelocityLimits, - bool _hasAccelerationLimits, - bool _hasJerkLimits, - double _minVelocity, - double _maxVelocity, - double _minAcceleration, - double _maxAcceleration, - double _minJerk, - double _maxJerk) - : hasVelocityLimits(_hasVelocityLimits), - hasAccelerationLimits(_hasAccelerationLimits), - hasJerkLimits(_hasJerkLimits), - minVelocity(_minVelocity), - maxVelocity(_maxVelocity), - minAcceleration(_minAcceleration), - maxAcceleration(_maxAcceleration), - minJerk(_minJerk), - maxJerk(_maxJerk) - { - } - - /// \brief Enable/Disable velocity. - public: bool hasVelocityLimits; - - /// \brief Enable/Disable acceleration. - public: bool hasAccelerationLimits; - - /// \brief Enable/Disable jerk. - public: bool hasJerkLimits; - - /// \brief Minimum velocity limit. - public: double minVelocity; - - /// \brief Maximum velocity limit. - public: double maxVelocity; - - /// \brief Minimum acceleration limit. - public: double minAcceleration; - - /// \brief Maximum acceleration limit. - public: double maxAcceleration; - - /// \brief Minimum jerk limit. - public: double minJerk; - - /// \brief Maximum jerk limit. - public: double maxJerk; -}; - -////////////////////////////////////////////////// -SpeedLimiter::SpeedLimiter(bool _hasVelocityLimits, - bool _hasAccelerationLimits, - bool _hasJerkLimits, - double _minVelocity, - double _maxVelocity, - double _minAcceleration, - double _maxAcceleration, - double _minJerk, - double _maxJerk) - : dataPtr(std::make_unique<SpeedLimiterPrivate>(_hasVelocityLimits, - _hasAccelerationLimits, _hasJerkLimits, _minVelocity, _maxVelocity, - _minAcceleration, _maxAcceleration, _minJerk, _maxJerk)) -{ -} - -////////////////////////////////////////////////// -SpeedLimiter::~SpeedLimiter() -{ -} - -////////////////////////////////////////////////// -double SpeedLimiter::Limit(double &_v, double _v0, double _v1, double _dt) const -{ - const double tmp = _v; - - this->LimitJerk(_v, _v0, _v1, _dt); - this->LimitAcceleration(_v, _v0, _dt); - this->LimitVelocity(_v); - - if (ignition::math::equal(tmp, 0.0)) - return 1.0; - else - return _v / tmp; -} - -////////////////////////////////////////////////// -double SpeedLimiter::LimitVelocity(double &_v) const -{ - const double tmp = _v; - - if (this->dataPtr->hasVelocityLimits) - { - _v = ignition::math::clamp( - _v, this->dataPtr->minVelocity, this->dataPtr->maxVelocity); - } - - if (ignition::math::equal(tmp, 0.0)) - return 1.0; - else - return _v / tmp; -} - -////////////////////////////////////////////////// -double SpeedLimiter::LimitAcceleration(double &_v, double _v0, double _dt) const -{ - const double tmp = _v; - - if (this->dataPtr->hasAccelerationLimits) - { - const double dvMin = this->dataPtr->minAcceleration * _dt; - const double dvMax = this->dataPtr->maxAcceleration * _dt; - - const double dv = ignition::math::clamp(_v - _v0, dvMin, dvMax); - - _v = _v0 + dv; - } - - if (ignition::math::equal(tmp, 0.0)) - return 1.0; - else - return _v / tmp; -} - -////////////////////////////////////////////////// -double SpeedLimiter::LimitJerk(double &_v, double _v0, double _v1, double _dt) - const -{ - const double tmp = _v; - - if (this->dataPtr->hasJerkLimits) - { - const double dv = _v - _v0; - const double dv0 = _v0 - _v1; - - const double dt2 = 2. * _dt * _dt; - - const double daMin = this->dataPtr->minJerk * dt2; - const double daMax = this->dataPtr->maxJerk * dt2; - - const double da = ignition::math::clamp(dv - dv0, daMin, daMax); - - _v = _v0 + dv0 + da; - } - - if (ignition::math::equal(tmp, 0.0)) - return 1.0; - else - return _v / tmp; -} diff --git a/src/systems/diff_drive/SpeedLimiter.hh b/src/systems/diff_drive/SpeedLimiter.hh deleted file mode 100644 index 34286bc3ae..0000000000 --- a/src/systems/diff_drive/SpeedLimiter.hh +++ /dev/null @@ -1,130 +0,0 @@ -/********************************************************************* - * Software License Agreement (BSD License) - * - * Copyright (c) 2013, PAL Robotics, S.L. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * * Neither the name of the PAL Robotics nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *********************************************************************/ - -/* - * Author: Enrique Fernández - * Modified: Carlos Agüero - */ - -#ifndef IGNITION_GAZEBO_SYSTEMS_SPEEDLIMITER_HH_ -#define IGNITION_GAZEBO_SYSTEMS_SPEEDLIMITER_HH_ - -#include <memory> - -#include <ignition/gazebo/System.hh> - -namespace ignition -{ -namespace gazebo -{ -// Inline bracket to help doxygen filtering. -inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { -namespace systems -{ - // Forward declaration. - class SpeedLimiterPrivate; - - /// \brief Class to limit velocity, acceleration and jerk. - /// \ref https://github.com/ros-controls/ros_controllers/tree/melodic-devel/diff_drive_controller - class SpeedLimiter - { - /// \brief Constructor. - /// \param [in] _hasVelocityLimits if true, applies velocity limits. - /// \param [in] _hasAccelerationLimits if true, applies acceleration limits. - /// \param [in] _hasJerkLimits if true, applies jerk limits. - /// \param [in] _minVelocity Minimum velocity [m/s], usually <= 0. - /// \param [in] _maxVelocity Maximum velocity [m/s], usually >= 0. - /// \param [in] _minAcceleration Minimum acceleration [m/s^2], usually <= 0. - /// \param [in] _maxAcceleration Maximum acceleration [m/s^2], usually >= 0. - /// \param [in] _minJerk Minimum jerk [m/s^3], usually <= 0. - /// \param [in] _maxJerk Maximum jerk [m/s^3], usually >= 0. - public: SpeedLimiter(bool _hasVelocityLimits = false, - bool _hasAccelerationLimits = false, - bool _hasJerkLimits = false, - double _minVelocity = 0.0, - double _maxVelocity = 0.0, - double _minAcceleration = 0.0, - double _maxAcceleration = 0.0, - double _minJerk = 0.0, - double _maxJerk = 0.0); - - /// \brief Destructor. - public: ~SpeedLimiter(); - - /// \brief Limit the velocity and acceleration. - /// \param [in, out] _v Velocity [m/s]. - /// \param [in] _v0 Previous velocity to v [m/s]. - /// \param [in] _v1 Previous velocity to v0 [m/s]. - /// \param [in] _dt Time step [s]. - /// \return Limiting factor (1.0 if none). - public: double Limit(double &_v, - double _v0, - double _v1, - double _dt) const; - - /// \brief Limit the velocity. - /// \param [in, out] _v Velocity [m/s]. - /// \return Limiting factor (1.0 if none). - public: double LimitVelocity(double &_v) const; - - /// \brief Limit the acceleration. - /// \param [in, out] _v Velocity [m/s]. - /// \param [in] _v0 Previous velocity [m/s]. - /// \param [in] _dt Time step [s]. - /// \return Limiting factor (1.0 if none). - public: double LimitAcceleration(double &_v, - double _v0, - double _dt) const; - - /// \brief Limit the jerk. - /// \param [in, out] _v Velocity [m/s]. - /// \param [in] _v0 Previous velocity to v [m/s]. - /// \param [in] _v1 Previous velocity to v0 [m/s]. - /// \param [in] _dt Time step [s]. - /// \return Limiting factor (1.0 if none). - /// \see http://en.wikipedia.org/wiki/Jerk_%28physics%29#Motion_control. - public: double LimitJerk(double &_v, - double _v0, - double _v1, - double _dt) const; - - /// \brief Private data pointer. - private: std::unique_ptr<SpeedLimiterPrivate> dataPtr; - }; - } -} -} -} - -#endif diff --git a/test/integration/diff_drive_system.cc b/test/integration/diff_drive_system.cc index df756fcd52..067476bfb0 100644 --- a/test/integration/diff_drive_system.cc +++ b/test/integration/diff_drive_system.cc @@ -54,145 +54,165 @@ class DiffDriveTest : public ::testing::TestWithParam<int> const std::string &_cmdVelTopic, const std::string &_odomTopic) { - // Start server - ServerConfig serverConfig; - serverConfig.SetSdfFile(_sdfFile); - - Server server(serverConfig); - EXPECT_FALSE(server.Running()); - EXPECT_FALSE(*server.Running(0)); - - // Create a system that records the vehicle poses - test::Relay testSystem; - - std::vector<math::Pose3d> poses; - testSystem.OnPostUpdate([&poses](const gazebo::UpdateInfo &, - const gazebo::EntityComponentManager &_ecm) - { - auto id = _ecm.EntityByComponents( - components::Model(), - components::Name("vehicle")); - EXPECT_NE(kNullEntity, id); + /// \param[in] forward If forward is true, the 'max_acceleration' + // and 'max_velocity' properties are tested, as the movement + // is forward, otherwise 'min_acceleration' and 'min_velocity' + // properties are tested. + auto testCmdVel = [&](bool forward){ + // Start server + ServerConfig serverConfig; + serverConfig.SetSdfFile(_sdfFile); + + Server server(serverConfig); + EXPECT_FALSE(server.Running()); + EXPECT_FALSE(*server.Running(0)); + + // Create a system that records the vehicle poses + test::Relay testSystem; + + std::vector<math::Pose3d> poses; + testSystem.OnPostUpdate([&poses](const gazebo::UpdateInfo &, + const gazebo::EntityComponentManager &_ecm) + { + auto id = _ecm.EntityByComponents( + components::Model(), + components::Name("vehicle")); + EXPECT_NE(kNullEntity, id); - auto poseComp = _ecm.Component<components::Pose>(id); - ASSERT_NE(nullptr, poseComp); + auto poseComp = _ecm.Component<components::Pose>(id); + ASSERT_NE(nullptr, poseComp); - poses.push_back(poseComp->Data()); - }); - server.AddSystem(testSystem.systemPtr); + poses.push_back(poseComp->Data()); + }); + server.AddSystem(testSystem.systemPtr); - // Run server and check that vehicle didn't move - server.Run(true, 1000, false); + // Run server and check that vehicle didn't move + server.Run(true, 1000, false); - EXPECT_EQ(1000u, poses.size()); + EXPECT_EQ(1000u, poses.size()); - for (const auto &pose : poses) - { - EXPECT_EQ(poses[0], pose); - } - - // Get odometry messages - double period{1.0 / 50.0}; - double lastMsgTime{1.0}; - std::vector<math::Pose3d> odomPoses; - std::function<void(const msgs::Odometry &)> odomCb = - [&](const msgs::Odometry &_msg) + for (const auto &pose : poses) { - ASSERT_TRUE(_msg.has_header()); - ASSERT_TRUE(_msg.header().has_stamp()); - - double msgTime = - static_cast<double>(_msg.header().stamp().sec()) + - static_cast<double>(_msg.header().stamp().nsec()) * 1e-9; - - EXPECT_DOUBLE_EQ(msgTime, lastMsgTime + period); - lastMsgTime = msgTime; - - odomPoses.push_back(msgs::Convert(_msg.pose())); - }; - - // Publish command and check that vehicle moved - transport::Node node; - auto pub = node.Advertise<msgs::Twist>(_cmdVelTopic); - node.Subscribe(_odomTopic, odomCb); - - msgs::Twist msg; - - // Avoid wheel slip by limiting acceleration - // Avoid wheel slip by limiting acceleration (1 m/s^2) - // and max velocity (0.5 m/s). - // See <max_velocity< and <max_aceleration> parameters - // in "/test/worlds/diff_drive.sdf". - test::Relay velocityRamp; - const double desiredLinVel = 10.5; - const double desiredAngVel = 0.2; - velocityRamp.OnPreUpdate( - [&](const gazebo::UpdateInfo &/*_info*/, - const gazebo::EntityComponentManager &) + EXPECT_EQ(poses[0], pose); + } + + // Get odometry messages + double period{1.0 / 50.0}; + double lastMsgTime{1.0}; + std::vector<math::Pose3d> odomPoses; + std::function<void(const msgs::Odometry &)> odomCb = + [&](const msgs::Odometry &_msg) { - msgs::Set(msg.mutable_linear(), - math::Vector3d(desiredLinVel, 0, 0)); - msgs::Set(msg.mutable_angular(), - math::Vector3d(0.0, 0, desiredAngVel)); - pub.Publish(msg); - }); - - server.AddSystem(velocityRamp.systemPtr); - - server.Run(true, 3000, false); + ASSERT_TRUE(_msg.has_header()); + ASSERT_TRUE(_msg.header().has_stamp()); + + double msgTime = + static_cast<double>(_msg.header().stamp().sec()) + + static_cast<double>(_msg.header().stamp().nsec()) * 1e-9; + + EXPECT_DOUBLE_EQ(msgTime, lastMsgTime + period); + lastMsgTime = msgTime; + + odomPoses.push_back(msgs::Convert(_msg.pose())); + }; + + // Publish command and check that vehicle moved + transport::Node node; + auto pub = node.Advertise<msgs::Twist>(_cmdVelTopic); + node.Subscribe(_odomTopic, odomCb); + + msgs::Twist msg; + + // Avoid wheel slip by limiting acceleration (1 m/s^2) + // and max velocity (0.5 m/s). + // See <max_velocity> and <max_aceleration> parameters + // in "/test/worlds/diff_drive.sdf". + // See <min_velocity>, <min_aceleration>, <min_jerk> and + // <max_jerk> parameters in "/test/worlds/diff_drive.sdf". + test::Relay velocityRamp; + const int movementDirection = (forward ? 1 : -1); + double desiredLinVel = movementDirection * 10.5; + double desiredAngVel = 0.2; + velocityRamp.OnPreUpdate( + [&](const gazebo::UpdateInfo &/*_info*/, + const gazebo::EntityComponentManager &) + { + msgs::Set(msg.mutable_linear(), + math::Vector3d(desiredLinVel, 0, 0)); + msgs::Set(msg.mutable_angular(), + math::Vector3d(0.0, 0, desiredAngVel)); + pub.Publish(msg); + }); + + server.AddSystem(velocityRamp.systemPtr); + + server.Run(true, 3000, false); + + // Poses for 4s + ASSERT_EQ(4000u, poses.size()); + + int sleep = 0; + int maxSleep = 30; + for (; odomPoses.size() < 150 && sleep < maxSleep; ++sleep) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + ASSERT_NE(maxSleep, sleep); + + // Odometry calculates the pose of a point that is located half way + // between the two wheels, not the origin of the model. For example, + // if the vehicle is commanded to rotate in place, the vehicle will + // rotate about the point half way between the two wheels, thus, + // the odometry position will remain zero. + // However, since the model origin is offset, the model position will + // change. To find the final pose of the model, we have to do the + // following similarity transformation + math::Pose3d tOdomModel(0.554283, 0, -0.325, 0, 0, 0); + auto finalModelFramePose = + tOdomModel * odomPoses.back() * tOdomModel.Inverse(); + + // Odom for 3s + ASSERT_FALSE(odomPoses.empty()); + EXPECT_EQ(150u, odomPoses.size()); + + auto expectedLowerPosition = + (forward ? poses[0].Pos() : poses[3999].Pos()); + auto expectedGreaterPosition = + (forward ? poses[3999].Pos() : poses[0].Pos()); + + EXPECT_LT(expectedLowerPosition.X(), expectedGreaterPosition.X()); + EXPECT_LT(expectedLowerPosition.Y(), expectedGreaterPosition.Y()); + EXPECT_NEAR(expectedLowerPosition.Z(), expectedGreaterPosition.Z(), tol); + EXPECT_NEAR(poses[0].Rot().X(), poses[3999].Rot().X(), tol); + EXPECT_NEAR(poses[0].Rot().Y(), poses[3999].Rot().Y(), tol); + EXPECT_LT(poses[0].Rot().Z(), poses[3999].Rot().Z()); + + // The value from odometry will be close, but not exactly the ground truth + // pose of the robot model. This is partially due to throttling the + // odometry publisher, partially due to a frame difference between the + // odom frame and the model frame, and partially due to wheel slip. + EXPECT_NEAR(poses[1020].Pos().X(), odomPoses[0].Pos().X(), 1e-2); + EXPECT_NEAR(poses[1020].Pos().Y(), odomPoses[0].Pos().Y(), 1e-2); + EXPECT_NEAR(poses.back().Pos().X(), finalModelFramePose.Pos().X(), 1e-2); + EXPECT_NEAR(poses.back().Pos().Y(), finalModelFramePose.Pos().Y(), 1e-2); + + // Verify velocity and acceleration boundaries. + // Moving time. + double t = 3.0; + double d = poses[3999].Pos().Distance(poses[0].Pos()); + double v0 = 0; + double v = d / t; + double a = (v - v0) / t; + + EXPECT_LT(v, 0.5); + EXPECT_LT(a, 1); + EXPECT_GT(v, -0.5); + EXPECT_GT(a, -1); - // Poses for 4s - ASSERT_EQ(4000u, poses.size()); + }; - int sleep = 0; - int maxSleep = 30; - for (; odomPoses.size() < 150 && sleep < maxSleep; ++sleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - ASSERT_NE(maxSleep, sleep); - - // Odometry calculates the pose of a point that is located half way between - // the two wheels, not the origin of the model. For example, if the - // vehicle is commanded to rotate in place, the vehicle will rotate about - // the point half way between the two wheels, thus, the odometry position - // will remain zero. - // However, since the model origin is offset, the model position will - // change. To find the final pose of the model, we have to do the - // following similarity transformation - math::Pose3d tOdomModel(0.554283, 0, -0.325, 0, 0, 0); - auto finalModelFramePose = - tOdomModel * odomPoses.back() * tOdomModel.Inverse(); - - // Odom for 3s - ASSERT_FALSE(odomPoses.empty()); - EXPECT_EQ(150u, odomPoses.size()); - - EXPECT_LT(poses[0].Pos().X(), poses[3999].Pos().X()); - EXPECT_LT(poses[0].Pos().Y(), poses[3999].Pos().Y()); - EXPECT_NEAR(poses[0].Pos().Z(), poses[3999].Pos().Z(), tol); - EXPECT_NEAR(poses[0].Rot().X(), poses[3999].Rot().X(), tol); - EXPECT_NEAR(poses[0].Rot().Y(), poses[3999].Rot().Y(), tol); - EXPECT_LT(poses[0].Rot().Z(), poses[3999].Rot().Z()); - - // The value from odometry will be close, but not exactly the ground truth - // pose of the robot model. This is partially due to throttling the - // odometry publisher, partially due to a frame difference between the - // odom frame and the model frame, and partially due to wheel slip. - EXPECT_NEAR(poses[1020].Pos().X(), odomPoses[0].Pos().X(), 1e-2); - EXPECT_NEAR(poses[1020].Pos().Y(), odomPoses[0].Pos().Y(), 1e-2); - EXPECT_NEAR(poses.back().Pos().X(), finalModelFramePose.Pos().X(), 1e-2); - EXPECT_NEAR(poses.back().Pos().Y(), finalModelFramePose.Pos().Y(), 1e-2); - - // Max velocities/accelerations expectations. - // Moving time. - double t = 3.0; - double d = poses[3999].Pos().Distance(poses[1000].Pos()); - const double v0 = 0; - double v = d / t; - double a = (v - v0) / t; - EXPECT_LT(v, 0.5); - EXPECT_LT(a, 1); + testCmdVel(true /*test forward movement*/); + testCmdVel(false /*test backward movement*/); } }; diff --git a/test/worlds/diff_drive.sdf b/test/worlds/diff_drive.sdf index b2b00bb0c4..dcb6a1fc2e 100644 --- a/test/worlds/diff_drive.sdf +++ b/test/worlds/diff_drive.sdf @@ -228,7 +228,9 @@ <wheel_separation>1.25</wheel_separation> <wheel_radius>0.3</wheel_radius> <max_acceleration>1</max_acceleration> + <min_acceleration>-1</min_acceleration> <max_velocity>0.5</max_velocity> + <min_velocity>-0.5</min_velocity> <!-- no odom_publisher_frequency defaults to 50 Hz --> </plugin> diff --git a/test/worlds/diff_drive_custom_topics.sdf b/test/worlds/diff_drive_custom_topics.sdf index 075c1c6744..15a414d1e3 100644 --- a/test/worlds/diff_drive_custom_topics.sdf +++ b/test/worlds/diff_drive_custom_topics.sdf @@ -231,6 +231,8 @@ <odom_topic>/model/bar/odom</odom_topic> <max_acceleration>1</max_acceleration> <max_velocity>0.5</max_velocity> + <min_acceleration>-1</min_acceleration> + <min_velocity>-0.5</min_velocity> <!-- no odom_publisher_frequency defaults to 50 Hz --> </plugin>