pxmlw6n2f/Gazebo_Distributed_TCP/test/integration/imu.cc

847 lines
32 KiB
C++

/*
* Copyright (C) 2012 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <ignition/math/Rand.hh>
#include "gazebo/test/ServerFixture.hh"
#include "gazebo/sensors/sensors.hh"
#include "gazebo/common/common.hh"
#include "gazebo/test/helper_physics_generator.hh"
// How tightly to compare for deterministic values
#define IMU_TOL 1e-5
using namespace gazebo;
class ImuTest : public ServerFixture,
public testing::WithParamInterface<const char*>
{
/// \brief start imu_sensor_test.world, which contains a pendulum,
/// a sphere with frictional contact, a sphere with frictionless
/// contact and a ramp. Each model has an IMU attached.
/// This test check results to make sure the readings adhere to
/// each simple model under gravity.
public: void ImuSensorTestWorld(const std::string &_physicsEngine);
/// \brief Spawn a static model with an ImuSensor attached
/// in the empty world. Test basic IMU outputs.
public: void Stationary_EmptyWorld(const std::string &_physicsEngine);
/// \brief Spawn a static model with an ImuSensor attached
/// in the empty world. Test basic IMU outputs with noise enabled.
public: void Stationary_EmptyWorld_Noise(const std::string &_physicsEngine);
/// \brief Spawn a static model with an ImuSensor attached
/// in the empty world. Test basic IMU outputs with bias enabled.
public: void Stationary_EmptyWorld_Bias(const std::string &_physicsEngine);
/// \breif Return gravity rotated by some orientation
/// \param[in] _rot User specified rotation
/// \param[out] _g gravity in user specified orientation
private: void GetGravity(const math::Quaternion& _rot, math::Vector3 &_g);
/// \breif Collect a number of samples and return the average
/// rate and accel values
/// \param[in] _imu Pointer to sensor
/// \param[in] _cnt number of samples to tak
/// \param[out] _rateMean average angular rates over samples
/// \param[out] _accelMean average accelerations over samples
/// \param[out] _orientation orientation of the imu at the end of sample
/// period
private: void GetImuData(sensors::ImuSensorPtr _imu, unsigned int _cnt,
ignition::math::Vector3d &_rateMean,
ignition::math::Vector3d &_accelMean,
ignition::math::Quaterniond &_orientation);
};
void ImuTest::GetGravity(const math::Quaternion &_rot, math::Vector3 &_g)
{
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
ASSERT_TRUE(physics != NULL);
// Rotate into IMU's frame
_g = _rot.GetInverse().RotateVector(physics->GetGravity());
}
void ImuTest::GetImuData(sensors::ImuSensorPtr _imu,
unsigned int _cnt,
ignition::math::Vector3d &_rateMean,
ignition::math::Vector3d &_accelMean,
ignition::math::Quaterniond &_orientation)
{
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
// Collect a number of samples and return the average rate and accel values
ignition::math::Vector3d rateSum, accelSum;
for (unsigned int i = 0; i < _cnt; ++i)
{
world->Step(1);
int j = 0;
while (_imu->LastMeasurementTime() == gazebo::common::Time::Zero &&
j < 100)
{
_imu->Update(true);
gazebo::common::Time::MSleep(100);
++j;
}
EXPECT_LT(j, 100);
rateSum += _imu->AngularVelocity();
accelSum += _imu->LinearAcceleration();
}
_rateMean = rateSum / _cnt;
_accelMean = accelSum / _cnt;
_orientation = _imu->Orientation();
}
void ImuTest::ImuSensorTestWorld(const std::string &_physicsEngine)
{
if (_physicsEngine != "ode")
{
gzerr << "not working yet for anything other than ode. see issue #893.\n";
return;
}
Load("worlds/imu_sensor_test.world", true, _physicsEngine);
// get world
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
// get physics engine
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
ASSERT_TRUE(physics != NULL);
// get pendulum
std::string pendulumName = "model_pendulum";
physics::ModelPtr pendulumModel = world->GetModel(pendulumName);
ASSERT_TRUE(pendulumModel != NULL);
std::string pendulumSensorName = "pendulum_imu_sensor";
sensors::ImuSensorPtr pendulumImu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(pendulumSensorName));
ASSERT_TRUE(pendulumImu != NULL);
pendulumImu->Init();
// get friction ball
std::string ballFrictionName = "model_ball";
physics::ModelPtr ballFrictionModel = world->GetModel(ballFrictionName);
ASSERT_TRUE(ballFrictionModel != NULL);
std::string ballFrictionSensorName = "ball_imu_sensor";
sensors::ImuSensorPtr ballFrictionImu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(ballFrictionSensorName));
ASSERT_TRUE(ballFrictionImu != NULL);
ballFrictionImu->Init();
// get frictionless ball
std::string ballNoFrictionName = "model_ball_no_friction";
physics::ModelPtr ballNoFrictionModel = world->GetModel(ballNoFrictionName);
ASSERT_TRUE(ballNoFrictionModel != NULL);
std::string ballNoFrictionSensorName = "ball_no_friction_imu_sensor";
sensors::ImuSensorPtr ballNoFrictionImu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(ballNoFrictionSensorName));
ASSERT_TRUE(ballNoFrictionImu != NULL);
ballNoFrictionImu->Init();
// get floating ball
std::string ballFloatingName = "model_floating_imu";
physics::ModelPtr ballFloatingModel = world->GetModel(ballFloatingName);
ASSERT_TRUE(ballFloatingModel != NULL);
std::string ballFloatingSensorName = "ball_floating_imu_sensor";
sensors::ImuSensorPtr ballFloatingImu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(ballFloatingSensorName));
ASSERT_TRUE(ballFloatingImu != NULL);
ballFloatingImu->Init();
// get floating ball 2
std::string ballFloatingName2 = "link_floating_imu_2";
physics::LinkPtr ballFloatingLink2 =
ballFloatingModel->GetLink(ballFloatingName2);
ASSERT_TRUE(ballFloatingLink2 != NULL);
std::string ballFloatingSensorName2 = "ball_floating_imu_sensor_2";
sensors::ImuSensorPtr ballFloatingImu2 =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(ballFloatingSensorName2));
ASSERT_TRUE(ballFloatingImu2 != NULL);
ballFloatingImu2->Init();
// get gravity
math::Vector3 g = physics->GetGravity();
// run for 1900 steps (Step(1) each time), or 1.9 seconds, enough
// to capture what we need from this experiment.
for (unsigned n = 0; n < 1900; ++n)
{
world->Step(1);
// gzdbg << "time: " << world->GetSimTime().Double() << "\n";
// pendulum
// on startup
// sensor linear accel [0 0 0]
// Link::GetRelativeLinearAccel() [0 0 -9.81]
// Link::GetWorldLinearAccel() [0 0 -9.81]
// @T=1.872 sec, at max lowest position
// sensor linear accel [-0 -0.041216 29.4258]
// Link::GetRelativeLinearAccel() [-0 -0.008923 19.6159]
// Link::GetWorldLinearAccel() [-0 0.055649 19.6158]
{
// get states from imu sensor
ignition::math::Vector3d imuLinearAccel =
pendulumImu->LinearAcceleration();
// get states from link
math::Vector3 relativeLinearAccel =
pendulumModel->GetRelativeLinearAccel();
math::Vector3 worldLinearAccel =
pendulumModel->GetWorldLinearAccel();
if (world->GetSimTime().Double() == 1.872)
{
// initial values
EXPECT_NEAR(imuLinearAccel.X(), 0, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Y(), -0.041216, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Z(), 29.42581726, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.y, -0.036397, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.z, 19.6158848, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.y, -0.0267709, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.z, 19.6159003, IMU_TOL);
}
else
{
// initial values
EXPECT_LE(imuLinearAccel.Z(), 29.4259);
EXPECT_LE(relativeLinearAccel.z, 19.616);
EXPECT_LE(worldLinearAccel.z, 19.616);
}
}
// friction ball
// before contact
// sensor linear accel [0 0 0]
// Link::GetRelativeLinearAccel() [0 0 -9.81]
// Link::GetWorldLinearAccel() [0 0 -9.81]
//
// @T=1.2 sec, on ramp - varies, e.g.
// sensor linear accel [-7.81558 0 3.71003]
// Link::GetRelativeLinearAccel() [1.98569 0 3.29613]
// Link::GetWorldLinearAccel() [3.37698 0 -1.84485]
//
// @T=1.849 sec, on ground - sensor vector rotates
// sensor linear accel [-2.93844 0 9.35958]
// Link::GetRelativeLinearAccel() [0 0 0]
// Link::GetWorldLinearAccel() [0 0 0]
{
// get states from imu sensor
ignition::math::Vector3d imuLinearAccel =
ballFrictionImu->LinearAcceleration();
// get states from link
math::Vector3 relativeLinearAccel =
ballFrictionModel->GetRelativeLinearAccel();
math::Vector3 worldLinearAccel =
ballFrictionModel->GetWorldLinearAccel();
if (world->GetSimTime().Double() <= 1.0)
{
// freefall
EXPECT_NEAR(imuLinearAccel.X(), 0, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Y(), 0, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Z(), 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.x, g.x, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.y, g.y, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.z, g.z, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.x, g.x, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.y, g.y, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.z, g.z, IMU_TOL);
}
// should use contact detector for these timing stuff
else if (world->GetSimTime().Double() >= 1.2 &&
world->GetSimTime().Double() <= 1.84)
{
// on ramp
// ...hm, not much can be said in simple terms, leave out for now.
}
else if (world->GetSimTime().Double() >= 1.85)
{
// on the ground
double imuMag = imuLinearAccel.Length();
double gMag = g.GetLength();
EXPECT_NEAR(imuMag, gMag, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.y, 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.z, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.y, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.z, 0, IMU_TOL);
}
}
// frictionless ball
// before contact
// sensor linear accel [0 0 0]
// Link::GetRelativeLinearAccel() [0 0 -9.81]
// Link::GetWorldLinearAccel() [0 0 -9.81]
// @T=1.2 sec, on ramp - constant
// sensor linear accel [4.12742 0 7.55518]
// Link::GetRelativeLinearAccel() [4.12742 0 -2.25482]
// Link::GetWorldLinearAccel() [4.12742 0 -2.25482]
// @T=1.8 sec, on ground - constant
// sensor linear accel [0 0 9.81]
// Link::GetRelativeLinearAccel() [0 0 0]
// Link::GetWorldLinearAccel() [0 0 0]
{
// get states from imu sensor
ignition::math::Vector3d imuLinearAccel =
ballNoFrictionImu->LinearAcceleration();
// get states from link
math::Vector3 relativeLinearAccel =
ballNoFrictionModel->GetRelativeLinearAccel();
math::Vector3 worldLinearAccel =
ballNoFrictionModel->GetWorldLinearAccel();
if (world->GetSimTime().Double() <= 1.0)
{
// freefall
EXPECT_NEAR(imuLinearAccel.X(), 0, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Y(), 0, IMU_TOL);
EXPECT_NEAR(imuLinearAccel.Z(), 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.x, g.x, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.y, g.y, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.z, g.z, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.x, g.x, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.y, g.y, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.z, g.z, IMU_TOL);
}
else if (world->GetSimTime().Double() >= 1.3 &&
world->GetSimTime().Double() <= 1.751)
{
// on the ramp
const double rampAngle = 0.5;
double gMag = g.GetLength();
double imuMag = imuLinearAccel.Length();
EXPECT_NEAR(imuMag, gMag*cos(rampAngle), IMU_TOL);
double relMag = relativeLinearAccel.GetLength();
EXPECT_NEAR(relMag, gMag*sin(rampAngle), IMU_TOL);
double worMag = worldLinearAccel.GetLength();
EXPECT_NEAR(worMag, gMag*sin(rampAngle), IMU_TOL);
}
else if (world->GetSimTime().Double() >= 1.8)
{
// on the ground
double imuMag = imuLinearAccel.Length();
double gMag = g.GetLength();
EXPECT_NEAR(imuMag, gMag, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.y, 0, IMU_TOL);
EXPECT_NEAR(relativeLinearAccel.z, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.x, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.y, 0, IMU_TOL);
EXPECT_NEAR(worldLinearAccel.z, 0, IMU_TOL);
}
}
}
// floating ball
// This "robot" starts up aligned with world axis.
// test that SetReferencePose resets orientation to identity
ballFloatingImu->SetReferencePose();
ignition::math::Quaterniond imuOrientation = ballFloatingImu->Orientation();
EXPECT_NEAR(ignition::math::Quaterniond::Identity.W(),
imuOrientation.W(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.X(),
imuOrientation.X(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.Y(),
imuOrientation.Y(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.Z(),
imuOrientation.Z(), IMU_TOL);
// test that SetReferenceOrientation sets orientation to argument
// in this test case, assume world is NWU (X-North, Y-West, Z-Up),
// then transform from NWU to NED is below:
ignition::math::Quaterniond nwuToNEDReference =
ignition::math::Quaterniond(M_PI, 0, 0);
// declare NED frame the reference frame for this IMU
ballFloatingImu->SetWorldToReferenceOrientation(nwuToNEDReference);
// let messages propagate asynchronously
world->Step(1000);
/****************************************************************************/
/* */
/* Static Pose Initialization Tests */
/* */
/****************************************************************************/
/* orientation of the imu in NED frame */
/****************************************************************************/
imuOrientation = ballFloatingImu->Orientation();
EXPECT_NEAR(imuOrientation.W(), 0, IMU_TOL);
EXPECT_NEAR(imuOrientation.X(), -1, IMU_TOL);
EXPECT_NEAR(imuOrientation.Y(), 0, IMU_TOL);
EXPECT_NEAR(imuOrientation.Z(), 0, IMU_TOL);
// imu orientation in world frame
ignition::math::Quaterniond imuWorldOrientation =
imuOrientation * nwuToNEDReference;
EXPECT_NEAR(imuWorldOrientation.W(), 1, IMU_TOL);
EXPECT_NEAR(imuWorldOrientation.X(), 0, IMU_TOL);
EXPECT_NEAR(imuWorldOrientation.Y(), 0, IMU_TOL);
EXPECT_NEAR(imuWorldOrientation.Z(), 0, IMU_TOL);
/****************************************************************************/
/* floating ball 2 */
/* This "robot" starts with a yaw of 1.8 rad from world frame. */
/* test that SetReferencePose resets orientation to identity */
/****************************************************************************/
ballFloatingImu2->SetReferencePose();
ignition::math::Quaterniond imuOrientation2 = ballFloatingImu2->Orientation();
EXPECT_NEAR(ignition::math::Quaterniond::Identity.W(),
imuOrientation2.W(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.X(),
imuOrientation2.X(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.Y(),
imuOrientation2.Y(), IMU_TOL);
EXPECT_NEAR(ignition::math::Quaterniond::Identity.Z(),
imuOrientation2.Z(), IMU_TOL);
/****************************************************************************/
/* */
/* Static Pose Manipulation Tests */
/* */
/****************************************************************************/
/* test that SetReferenceOrientation sets orientation to argument */
/* in this test case, assume world is NWU (X-North, Y-West, Z-Up), */
/* then transform from NWU to NED is below: */
/****************************************************************************/
// declare NED frame the reference frame for this IMU
ballFloatingImu2->SetWorldToReferenceOrientation(nwuToNEDReference);
// let messages propagate asynchronously
world->Step(1000);
// orientation of the imu in NED frame
const double imu2Angle = 1.8;
imuOrientation2 = ballFloatingImu2->Orientation();
ignition::math::Vector3d rpy2 = imuOrientation2.Euler();
EXPECT_NEAR(fabs(rpy2.X()), M_PI, IMU_TOL);
EXPECT_NEAR(rpy2.Y(), 0, IMU_TOL);
EXPECT_NEAR(rpy2.Z(), -imu2Angle, IMU_TOL);
// imu orientation in world frame
ignition::math::Quaterniond imuWorldOrientation2 =
imuOrientation2 * nwuToNEDReference;
ignition::math::Vector3d rpyWorld2 = imuWorldOrientation2.Euler();
EXPECT_NEAR(rpyWorld2.X(), 0, IMU_TOL);
EXPECT_NEAR(rpyWorld2.Y(), 0, IMU_TOL);
EXPECT_NEAR(rpyWorld2.Z(), -imu2Angle, IMU_TOL);
/****************************************************************************/
/* */
/* Kinematic Pose Manipulation Test */
/* */
/****************************************************************************/
/* turn floating ball 2 by -1.8 rad yaw, and see if two floating */
/* balls orientation match */
/****************************************************************************/
ballFloatingLink2->SetWorldPose(
ignition::math::Pose3d(3.0, -3.40, 0.95, 0.0, 0.0, 0.0));
// let messages propagate asynchronously
world->Step(1000);
// get orientation of two floating balls and compare them
imuOrientation = ballFloatingImu->Orientation();
ignition::math::Vector3d rpy = imuOrientation.Euler();
imuOrientation2 = ballFloatingImu2->Orientation();
rpy2 = imuOrientation2.Euler();
EXPECT_NEAR(fabs(rpy.X()), fabs(rpy2.X()), IMU_TOL);
EXPECT_NEAR(rpy.Y(), rpy2.Y(), IMU_TOL);
EXPECT_NEAR(rpy.Z(), rpy2.Z(), IMU_TOL);
/****************************************************************************/
/* */
/* Dynamic Torque Test */
/* */
/****************************************************************************/
/* turn floating ball 2 by 0.6 rad yaw, apply torque about imu local Y-axis */
/* and test AngularVelocity is expressed in local frame. */
/****************************************************************************/
const double yaw = 0.6;
ballFloatingLink2->SetWorldPose(
ignition::math::Pose3d(3.0, -3.40, 0.95, 0.0, 0.0, yaw));
// HACK: take 100 steps, due to message passing synchronization delays,
// we should fix this by haing an equivalent blocking "service call" to
// SetWorldPose.
world->Step(100);
// Nm
const double tau = 150.0;
ballFloatingLink2->AddRelativeTorque(
ignition::math::Vector3d(0, tau, 0));
// expected velocity calculation
// kg*m^2
const double iyy = ballFloatingLink2->GetInertial()->GetPrincipalMoments().y;
EXPECT_NEAR(iyy, 0.1, 1e-6);
// sec
const double dt = world->GetPhysicsEngine()->GetMaxStepSize();
EXPECT_NEAR(dt, 0.001, 1e-6);
// 1.5 m/s , pitch rate
const double pDot = tau / iyy * dt;
const int nsteps = 1000;
const double p = pDot * (nsteps-1) * dt;
// let messages propagate asynchronously
world->Step(nsteps);
// get orientation of two floating balls and compare them
imuOrientation2 = ballFloatingImu2->Orientation();
rpy2 = imuOrientation2.Euler();
ignition::math::Vector3d rpyDot2 = ballFloatingImu2->AngularVelocity();
ignition::math::Vector3d linAcc2 = ballFloatingImu2->LinearAcceleration();
// compare against analytical results
// because NED
EXPECT_NEAR(fabs(rpy2.X()), M_PI, IMU_TOL);
EXPECT_NEAR(rpy2.Y(), -p, IMU_TOL);
EXPECT_NEAR(rpy2.Z(), -yaw, IMU_TOL);
EXPECT_NEAR(rpyDot2.X(), 0, IMU_TOL);
EXPECT_NEAR(rpyDot2.Y(), pDot, IMU_TOL);
EXPECT_NEAR(rpyDot2.Z(), 0, IMU_TOL);
// centripetal acceleration along radial (x-axis) direction.
// then add gravity acceleration to x and z based on orientation of imu.
this->GetGravity(ballFloatingImu2->Orientation(), g);
const double radius = boost::static_pointer_cast<physics::SphereShape>(
ballFloatingLink2 ->GetCollision(
"collision_sphere")->GetShape())->GetRadius();
// const double acc = -pDot*pDot*r;
const double acc = -rpyDot2.Y()*rpyDot2.Y()*radius;
const double accX = acc + g.x;
const double accZ = g.z;
EXPECT_NEAR(linAcc2.X(), accX, IMU_TOL);
EXPECT_NEAR(linAcc2.Y(), 0, IMU_TOL);
// FIXME: why is this error larger than default tol 1e-5?
// See ign-math issue #47.
// https://bitbucket.org/ignitionrobotics/ign-math/issues/47
const double special_IMU_TOL = 0.00016874990503534804;
EXPECT_NEAR(linAcc2.Z(), accZ, special_IMU_TOL);
/****************************************************************************/
/* */
/* Dynamic Linear Force Test */
/* */
/****************************************************************************/
/* turn floating ball 2 by 0.5 rad pitch, apply force about */
/* positive world z-axis */
/* and test if LinearAcceleration is expressed in local frame. */
/****************************************************************************/
const double pitch = 0.5;
ballFloatingImu2->SetWorldToReferenceOrientation(
ignition::math::Quaterniond());
ballFloatingLink2->Reset();
ballFloatingLink2->SetWorldPose(
ignition::math::Pose3d(3.0, -3.40, 0.95, 0.0, pitch, 0.0));
world->Step(100);
for (int i = 0; i < 1000; ++i)
{
const double f = 13.8;
ballFloatingLink2->AddForce(ignition::math::Vector3d(0, 0, f));
world->Step(1);
const double m = 5.0;
const double a = f / m;
ignition::math::Vector3d linAcc2 = ballFloatingImu2->LinearAcceleration();
this->GetGravity(ballFloatingImu2->Orientation(), g);
if (i > 100)
{
// THERE MUST BE A BETTER WAY TO DO THIS...
// need to take 100 stesps to ensure that
// imu readings are passed through from asynchronous transport
EXPECT_NEAR(linAcc2.X(), -a*sin(pitch) - g.x, IMU_TOL);
EXPECT_NEAR(linAcc2.Y(), 0 - g.y, IMU_TOL);
EXPECT_NEAR(linAcc2.Z(), a*cos(pitch) - g.z, IMU_TOL);
}
}
}
TEST_P(ImuTest, ImuSensorTestWorld)
{
ImuSensorTestWorld(GetParam());
}
void ImuTest::Stationary_EmptyWorld(const std::string &_physicsEngine)
{
// static models not fully working in simbody yet
if (_physicsEngine == "simbody")
{
gzerr << "Aborting test for Simbody, see issue #860.\n";
return;
}
Load("worlds/empty.world", true, _physicsEngine);
std::string modelName = "imu_model";
std::string imuSensorName = "imu_sensor";
math::Pose testPose(math::Vector3(0, 0, 0.05),
math::Quaternion(0.5, -1.0, 0.2));
SpawnImuSensor(modelName, imuSensorName, testPose.pos,
testPose.rot.GetAsEuler());
sensors::ImuSensorPtr imu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(imuSensorName));
ASSERT_TRUE(imu != NULL);
imu->Init();
ignition::math::Vector3d rateMean, accelMean;
ignition::math::Quaterniond orientation;
this->GetImuData(imu, 1, rateMean, accelMean, orientation);
EXPECT_NEAR(rateMean.X(), 0.0, IMU_TOL);
EXPECT_NEAR(rateMean.Y(), 0.0, IMU_TOL);
EXPECT_NEAR(rateMean.Z(), 0.0, IMU_TOL);
math::Vector3 g;
this->GetGravity(testPose.rot, g);
EXPECT_NEAR(accelMean.X(), -g.x, IMU_TOL);
EXPECT_NEAR(accelMean.Y(), -g.y, IMU_TOL);
EXPECT_NEAR(accelMean.Z(), -g.z, IMU_TOL);
// Orientation should be identity, since it is reported relative
// to reference pose.
EXPECT_NEAR(orientation.X(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Y(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Z(), 0, IMU_TOL);
EXPECT_NEAR(orientation.W(), 1, IMU_TOL);
}
TEST_P(ImuTest, EmptyWorld)
{
Stationary_EmptyWorld(GetParam());
}
void ImuTest::Stationary_EmptyWorld_Noise(const std::string &_physicsEngine)
{
// static models not fully working in simbody yet
if (_physicsEngine == "simbody")
{
gzerr << "Aborting test for Simbody, see issue #860.\n";
return;
}
Load("worlds/empty.world", true, _physicsEngine);
std::string modelName = "imu_model";
std::string imuSensorName = "imu_sensor";
math::Pose testPose(math::Vector3(0, 0, 0.05),
math::Quaternion(0.3, -1.4, 2.0));
double rateNoiseMean = 1.0;
double rateNoiseStddev = 0.1;
double rateBiasMean = 0.0;
double rateBiasStddev = 0.0;
double accelNoiseMean = -10.0;
double accelNoiseStddev = 0.1;
double accelBiasMean = 0.0;
double accelBiasStddev = 0.0;
SpawnImuSensor(modelName, imuSensorName, testPose.pos,
testPose.rot.GetAsEuler(), "gaussian",
rateNoiseMean, rateNoiseStddev,
rateBiasMean, rateBiasStddev,
accelNoiseMean, accelNoiseStddev,
accelBiasMean, accelBiasStddev);
sensors::ImuSensorPtr imu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(imuSensorName));
ASSERT_TRUE(imu != NULL);
imu->Init();
ignition::math::Vector3d rateMean, accelMean;
ignition::math::Quaterniond orientation;
this->GetImuData(imu, 1000, rateMean, accelMean, orientation);
double d1, d2;
// Have to account for the fact that the bias might be sampled as positive
// or negative
d1 = fabs(rateMean.X() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.X() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
d1 = fabs(rateMean.Y() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.Y() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
d1 = fabs(rateMean.Z() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.Z() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
math::Vector3 g;
this->GetGravity(testPose.rot, g);
// Have to account for the fact that the bias might be sampled as positive
// or negative
d1 = fabs(accelMean.X() - (accelNoiseMean + accelBiasMean) + g.x);
d2 = fabs(accelMean.X() - (accelNoiseMean - accelBiasMean) + g.x);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
d1 = fabs(accelMean.Y() - (accelNoiseMean + accelBiasMean) + g.y);
d2 = fabs(accelMean.Y() - (accelNoiseMean - accelBiasMean) + g.y);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
d1 = fabs(accelMean.Z() - (accelNoiseMean + accelBiasMean) + g.z);
d2 = fabs(accelMean.Z() - (accelNoiseMean - accelBiasMean) + g.z);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
// Orientation should be identity, since it is reported relative
// to reference pose.
EXPECT_NEAR(orientation.X(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Y(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Z(), 0, IMU_TOL);
EXPECT_NEAR(orientation.W(), 1, IMU_TOL);
}
TEST_P(ImuTest, EmptyWorldNoise)
{
Stationary_EmptyWorld_Noise(GetParam());
}
void ImuTest::Stationary_EmptyWorld_Bias(const std::string &_physicsEngine)
{
// static models not fully working in simbody yet
if (_physicsEngine == "simbody")
{
gzerr << "Aborting test for Simbody, see issue #860.\n";
return;
}
Load("worlds/empty.world", true, _physicsEngine);
std::string modelName = "imu_model";
std::string imuSensorName = "imu_sensor";
math::Pose testPose(math::Vector3(0, 0, 0.05),
math::Quaternion(-0.3, 0.5, 1.0));
double rateNoiseMean = 0.0;
double rateNoiseStddev = 0.0;
double rateBiasMean = 1.0;
double rateBiasStddev = 0.1;
double accelNoiseMean = 0.0;
double accelNoiseStddev = 0.0;
double accelBiasMean = 5.0;
double accelBiasStddev = 0.1;
SpawnImuSensor(modelName, imuSensorName, testPose.pos,
testPose.rot.GetAsEuler(), "gaussian",
rateNoiseMean, rateNoiseStddev,
rateBiasMean, rateBiasStddev,
accelNoiseMean, accelNoiseStddev,
accelBiasMean, accelBiasStddev);
sensors::ImuSensorPtr imu =
std::static_pointer_cast<sensors::ImuSensor>(
sensors::SensorManager::Instance()->GetSensor(imuSensorName));
ASSERT_TRUE(imu != NULL);
imu->Init();
ignition::math::Vector3d rateMean, accelMean;
ignition::math::Quaterniond orientation;
this->GetImuData(imu, 1000, rateMean, accelMean, orientation);
double d1, d2;
// Have to account for the fact that the bias might be sampled as positive
// or negative
d1 = fabs(rateMean.X() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.X() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
d1 = fabs(rateMean.Y() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.Y() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
d1 = fabs(rateMean.Z() - (rateNoiseMean + rateBiasMean));
d2 = fabs(rateMean.Z() - (rateNoiseMean - rateBiasMean));
EXPECT_NEAR(0.0, std::min(d1, d2),
3*rateNoiseStddev + 3*rateBiasStddev);
math::Vector3 g;
this->GetGravity(testPose.rot, g);
// Have to account for the fact that the bias might be sampled as positive
// or negative
d1 = fabs(accelMean.X() - (accelNoiseMean + accelBiasMean) + g.x);
d2 = fabs(accelMean.X() - (accelNoiseMean - accelBiasMean) + g.x);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
d1 = fabs(accelMean.Y() - (accelNoiseMean + accelBiasMean) + g.y);
d2 = fabs(accelMean.Y() - (accelNoiseMean - accelBiasMean) + g.y);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
d1 = fabs(accelMean.Z() - (accelNoiseMean + accelBiasMean) + g.z);
d2 = fabs(accelMean.Z() - (accelNoiseMean - accelBiasMean) + g.z);
EXPECT_NEAR(0.0, std::min(d1, d2),
3*accelNoiseStddev + 3*accelBiasStddev);
// Orientation should be identity, since it is reported relative
// to reference pose.
EXPECT_NEAR(orientation.X(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Y(), 0, IMU_TOL);
EXPECT_NEAR(orientation.Z(), 0, IMU_TOL);
EXPECT_NEAR(orientation.W(), 1, IMU_TOL);
}
TEST_P(ImuTest, EmptyWorldBias)
{
Stationary_EmptyWorld_Bias(GetParam());
}
INSTANTIATE_TEST_CASE_P(PhysicsEngines, ImuTest, PHYSICS_ENGINE_VALUES);
int main(int argc, char **argv)
{
// Set a specific seed to avoid occasional test failures due to
// statistically unlikely, but possible results.
ignition::math::Rand::Seed(42);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}