Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supply world frame orientation and heading to IMU sensor #1320

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1d40eb3
Added logic to set heading
adityapande-1995 Jan 25, 2022
79c146f
Address not mapped to object error in test world
adityapande-1995 Jan 27, 2022
ac691ba
Resolved segfault
adityapande-1995 Feb 3, 2022
4380847
Added integration test
adityapande-1995 Feb 4, 2022
80a351c
Added integration test with heading_deg
adityapande-1995 Feb 4, 2022
e9c34cf
Fixed a typo
adityapande-1995 Feb 4, 2022
9b75db0
Integration test for CUSTOM reference frame
adityapande-1995 Feb 4, 2022
df9b26a
Minor cleanup
adityapande-1995 Feb 7, 2022
fffd3f3
Added gaussian noise and odometry with covariance publisher
adityapande-1995 Mar 16, 2022
72af550
Added test case
adityapande-1995 Mar 16, 2022
8362ecf
Updated a minor comment
adityapande-1995 Mar 16, 2022
9163fdd
Test checks the covariance matrix
adityapande-1995 Mar 16, 2022
2642391
Clear test msgs before running the server, minor cleanup, remoned hea…
adityapande-1995 Mar 22, 2022
8453db0
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Mar 22, 2022
7707c23
Minor cleanup, docstring update
adityapande-1995 Mar 22, 2022
a2dc446
Changed quick return logic in Configure method, check for invalid pub…
adityapande-1995 Mar 22, 2022
752fdce
linter fixes
adityapande-1995 Mar 27, 2022
a7e3d7f
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
adityapande-1995 Mar 27, 2022
cc0af8d
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
adityapande-1995 Mar 28, 2022
f31bf4f
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Mar 29, 2022
fd20848
Orientation is reported wrt relative frame if orientation_ref_frame_t…
adityapande-1995 Mar 30, 2022
927e436
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Mar 30, 2022
2759a81
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
chapulina Mar 30, 2022
65f7129
Removed imu tag from sensors for default behaviour
adityapande-1995 Mar 30, 2022
e5974af
Added test:rotating body with pose offsets in model,link
adityapande-1995 Mar 31, 2022
4b0c004
Minor refactor
adityapande-1995 Mar 31, 2022
b2ce322
Added 3 models with different ref frames and poses
adityapande-1995 Mar 31, 2022
f021816
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
chapulina Apr 1, 2022
1d2033f
Require ign-msgs 8.3
chapulina Apr 1, 2022
cf78741
Style nits
chapulina Apr 1, 2022
17e77b0
more style
chapulina Apr 1, 2022
91c10b9
Bug fix in world file, minor style change
adityapande-1995 Apr 4, 2022
cf00994
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Apr 4, 2022
accb096
Fix for bionic
adityapande-1995 Apr 4, 2022
309f116
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
adityapande-1995 Apr 4, 2022
af673d1
Merge branch 'ign-gazebo6' into aditya/odom_pub_noise
adityapande-1995 Apr 4, 2022
a07eed3
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Apr 4, 2022
604e465
Merge branches 'aditya/odom_pub_noise' and 'aditya/named_frames_imu_s…
adityapande-1995 Apr 4, 2022
b49c5c8
parent_frame set to 'world' in custom_rpy
adityapande-1995 Apr 4, 2022
9b30abe
Merge branch 'ign-gazebo6' into aditya/named_frames_imu_system
adityapande-1995 Apr 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/systems/imu/Imu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <ignition/sensors/SensorFactory.hh>
#include <ignition/sensors/ImuSensor.hh>

#include "ignition/gazebo/World.hh"
#include "ignition/gazebo/components/AngularVelocity.hh"
#include "ignition/gazebo/components/Imu.hh"
#include "ignition/gazebo/components/Gravity.hh"
Expand Down Expand Up @@ -206,6 +207,28 @@ void ImuPrivate::AddSensor(
math::Pose3d p = worldPose(_entity, _ecm);
sensor->SetOrientationReference(p.Rot());

// Get world frame orientation and heading.
// If <orientation_reference_frame> includes a named
// frame like NED, that must be supplied to the IMU sensor,
// otherwise orientations are reported w.r.t to the initial
// orientation.
if (data.Element()->HasElement("imu")) {
auto imuElementPtr = data.Element()->GetElement("imu");
if (imuElementPtr->HasElement("orientation_reference_frame")) {
double heading = 0.0;

ignition::gazebo::World world(worldEntity);
if (world.SphericalCoordinates(_ecm))
{
auto sphericalCoordinates = world.SphericalCoordinates(_ecm).value();
heading = sphericalCoordinates.HeadingOffset().Radian();
}

sensor->SetWorldFrameOrientation(math::Quaterniond(0, 0, heading),
ignition::sensors::WorldFrameEnumType::ENU);
}
}
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved

// Set whether orientation is enabled
if (data.ImuSensor())
{
Expand Down
329 changes: 328 additions & 1 deletion test/integration/imu_system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,51 @@ class ImuTest : public InternalFixture<::testing::Test>

std::mutex mutex;
std::vector<msgs::IMU> imuMsgs;
msgs::IMU lastImuMsgENU;
msgs::IMU lastImuMsgNED;
msgs::IMU lastImuMsgNWU;
msgs::IMU lastImuMsgCUSTOM;
msgs::IMU lastImuMsgDEFAULT;

/////////////////////////////////////////////////
void imuENUCb(const msgs::IMU &_msg)
{
lastImuMsgENU = _msg;
}

/////////////////////////////////////////////////
void imuNEDCb(const msgs::IMU &_msg)
{
lastImuMsgNED = _msg;
}

/////////////////////////////////////////////////
void imuNWUCb(const msgs::IMU &_msg)
{
lastImuMsgNWU = _msg;
}

/////////////////////////////////////////////////
void imuCUSTOMCb(const msgs::IMU &_msg)
{
lastImuMsgCUSTOM = _msg;
}

/////////////////////////////////////////////////
void imuDEFULTCb(const msgs::IMU &_msg)
{
lastImuMsgDEFAULT = _msg;
}

/////////////////////////////////////////////////
void clearLastImuMsgs()
{
lastImuMsgCUSTOM.Clear();
lastImuMsgENU.Clear();
lastImuMsgNED.Clear();
lastImuMsgNWU.Clear();
lastImuMsgDEFAULT.Clear();
}

/////////////////////////////////////////////////
void imuCb(const msgs::IMU &_msg)
Expand Down Expand Up @@ -214,7 +259,7 @@ TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(ModelFalling))
}

/////////////////////////////////////////////////
// The test checks to make sure orientation is not published if it is deabled
// The test checks to make sure orientation is not published if it is disabled
TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(OrientationDisabled))
{
imuMsgs.clear();
Expand Down Expand Up @@ -250,3 +295,285 @@ TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(OrientationDisabled))
}
mutex.unlock();
}

/////////////////////////////////////////////////
// The test checks if the orientation is published according to the
// localization tag
TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(NamedFrames))
{
imuMsgs.clear();
clearLastImuMsgs();

// Start server
ServerConfig serverConfig;
const auto sdfFile = common::joinPaths(std::string(PROJECT_SOURCE_PATH),
"test", "worlds", "imu_named_frame.sdf");
serverConfig.SetSdfFile(sdfFile);

Server server(serverConfig);
EXPECT_FALSE(server.Running());
EXPECT_FALSE(*server.Running(0));

auto topicENU = "/imu_test_ENU";
auto topicNED = "/imu_test_NED";
auto topicNWU = "/imu_test_NWU";
auto topicCUSTOM = "/imu_test_CUSTOM";
auto topicDEFAULT = "/imu_test_DEFAULT";

// subscribe to imu topic
transport::Node node;
node.Subscribe(topicENU, &imuENUCb);
node.Subscribe(topicNED, &imuNEDCb);
node.Subscribe(topicNWU, &imuNWUCb);
node.Subscribe(topicCUSTOM, &imuCUSTOMCb);
node.Subscribe(topicDEFAULT, &imuDEFULTCb);

// Run server
server.Run(true, 200u, false);

// Check we received messages
EXPECT_TRUE(lastImuMsgENU.has_orientation());
EXPECT_TRUE(lastImuMsgNED.has_orientation());
EXPECT_TRUE(lastImuMsgNWU.has_orientation());
EXPECT_TRUE(lastImuMsgCUSTOM.has_orientation());
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_TRUE(lastImuMsgDEFAULT.has_orientation());

// For the DEFAULT msg, orientation is reported relative
// to the original pose, which should be identity quaternion.
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().z(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().w(), 1, 1e-2);

// For the ENU msg
EXPECT_NEAR(lastImuMsgENU.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().z(), 1, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().w(), 0, 1e-2);

// For the NED msg
EXPECT_NEAR(lastImuMsgNED.orientation().x(), -0.707, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().y(), 0.707, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().z(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().w(), 0, 1e-2);

// For the NWU msg
EXPECT_NEAR(lastImuMsgNWU.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().z(), 0.707, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().w(), 0.707, 1e-2);

// For the CUSTOM msg
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().y(), 0.707, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().z(), 0.707, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().w(), 0, 1e-2);
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
}

/////////////////////////////////////////////////
// The test checks if the orientation is published according to the
// localization tag, with heading_deg also accounted for
TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(NamedFramesWithHeading))
{
imuMsgs.clear();
clearLastImuMsgs();

// Start server
ServerConfig serverConfig;
const auto sdfFile = common::joinPaths(std::string(PROJECT_SOURCE_PATH),
"test", "worlds", "imu_heading_deg.sdf");
serverConfig.SetSdfFile(sdfFile);

Server server(serverConfig);
EXPECT_FALSE(server.Running());
EXPECT_FALSE(*server.Running(0));

auto topicENU = "/imu_test_ENU";
auto topicNED = "/imu_test_NED";
auto topicNWU = "/imu_test_NWU";
auto topicCUSTOM = "/imu_test_CUSTOM";
auto topicDEFAULT = "/imu_test_DEFAULT";

// subscribe to imu topic
transport::Node node;
node.Subscribe(topicENU, &imuENUCb);
node.Subscribe(topicNED, &imuNEDCb);
node.Subscribe(topicNWU, &imuNWUCb);
node.Subscribe(topicCUSTOM, &imuCUSTOMCb);
node.Subscribe(topicDEFAULT, &imuDEFULTCb);

// Run server
server.Run(true, 200u, false);

// Check we received messages
EXPECT_TRUE(lastImuMsgENU.has_orientation());
EXPECT_TRUE(lastImuMsgNED.has_orientation());
EXPECT_TRUE(lastImuMsgNWU.has_orientation());
EXPECT_TRUE(lastImuMsgCUSTOM.has_orientation());
EXPECT_TRUE(lastImuMsgDEFAULT.has_orientation());

// For the DEFAULT msg, orientation is reported relative
// to the original pose, which should be identity quaternion.
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().z(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgDEFAULT.orientation().w(), 1, 1e-2);

// For the ENU msg
EXPECT_NEAR(lastImuMsgENU.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().z(), 0.707, 1e-2);
EXPECT_NEAR(lastImuMsgENU.orientation().w(), 0.707, 1e-2);

// For the NED msg
EXPECT_NEAR(lastImuMsgNED.orientation().x(), -1, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().z(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNED.orientation().w(), 0, 1e-2);

// For the NWU msg
EXPECT_NEAR(lastImuMsgNWU.orientation().x(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().y(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().z(), 0, 1e-2);
EXPECT_NEAR(lastImuMsgNWU.orientation().w(), 1, 1e-2);

// For the CUSTOM msg
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().x(), -0.5, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().y(), 0.5, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().z(), 0.5, 1e-2);
EXPECT_NEAR(lastImuMsgCUSTOM.orientation().w(), 0.5, 1e-2);
}

/////////////////////////////////////////////////
// The test checks if orientations are reported correctly for a rotating body.
// The world includes a sphere rolling down a plane, with axis of rotation
// as the "west" direction vector, using the right hand rule.
TEST_F(ImuTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(RotatingBody))
{
imuMsgs.clear();
clearLastImuMsgs();

// Start server
ServerConfig serverConfig;
const auto sdfFile = common::joinPaths(std::string(PROJECT_SOURCE_PATH),
"test", "worlds", "imu_rotating_demo.sdf");
serverConfig.SetSdfFile(sdfFile);

Server server(serverConfig);
EXPECT_FALSE(server.Running());
EXPECT_FALSE(*server.Running(0));

msgs::IMU currentImuMsgDefault_1;
msgs::IMU currentImuMsgDefault_2;
msgs::IMU currentImuMsgDefault_3;

std::function<void(const msgs::IMU &_msg)> defaultCb1 =
[&](const msgs::IMU &_msg)
{
currentImuMsgDefault_1 = _msg;
};
std::function<void(const msgs::IMU &_msg)> defaultCb2 =
[&](const msgs::IMU &_msg)
{
currentImuMsgDefault_2 = _msg;
};
std::function<void(const msgs::IMU &_msg)> defaultCb3 =
[&](const msgs::IMU &_msg)
{
currentImuMsgDefault_3 = _msg;
};

// subscribe to imu topic
transport::Node node;
node.Subscribe("/imu_test_ENU", &imuENUCb);
node.Subscribe("/imu_test_NED", &imuNEDCb);
node.Subscribe("/imu_test_DEFAULT_1", defaultCb1);
node.Subscribe("/imu_test_DEFAULT_2", defaultCb2);
node.Subscribe("/imu_test_DEFAULT_3", defaultCb3);

// Run server
server.Run(true, 50u, false);

// Store initial orientations reported by the IMUs
auto initialOrientationDEFAULT_1 = msgs::Convert(
currentImuMsgDefault_1.orientation());
auto initialOrientationDEFAULT_2 = msgs::Convert(
currentImuMsgDefault_2.orientation());
auto initialOrientationDEFAULT_3 = msgs::Convert(
currentImuMsgDefault_3.orientation());
auto initialOrientationENU = msgs::Convert(
lastImuMsgENU.orientation());
auto initialOrientationNED = msgs::Convert(
lastImuMsgNED.orientation());

server.Run(true, 1500u, false);

// Store final orientations reported by the IMUs
auto finalOrientationDEFAULT_1 = msgs::Convert(
currentImuMsgDefault_1.orientation());
auto finalOrientationDEFAULT_2 = msgs::Convert(
currentImuMsgDefault_2.orientation());
auto finalOrientationDEFAULT_3 = msgs::Convert(
currentImuMsgDefault_3.orientation());
auto finalOrientationENU = msgs::Convert(
lastImuMsgENU.orientation());
auto finalOrientationNED = msgs::Convert(
lastImuMsgNED.orientation());

auto differenceOrientationDEFAULT_1 = finalOrientationDEFAULT_1 *
initialOrientationDEFAULT_1.Inverse();
auto differenceOrientationDEFAULT_2 = finalOrientationDEFAULT_2 *
initialOrientationDEFAULT_2.Inverse();
auto differenceOrientationDEFAULT_3 = finalOrientationDEFAULT_3 *
initialOrientationDEFAULT_3.Inverse();

auto differenceOrientationENU = finalOrientationENU *
initialOrientationENU.Inverse();
auto differenceOrientationNED = finalOrientationNED *
initialOrientationNED.Inverse();

// Since the sphere has rotated along the west direction,
// pitch and yaw change for ENU reporting IMU should be zero,
// and roll should have some non trivial negative value.
EXPECT_TRUE((differenceOrientationENU.Roll() < -0.04));
EXPECT_NEAR(differenceOrientationENU.Pitch(), 0, 1e-2);
EXPECT_NEAR(differenceOrientationENU.Yaw(), 0, 1e-2);

// Similarly, roll and yaw for NED reporting IMU should be zero,
// and pitch should be some non trivial negative value.
EXPECT_NEAR(differenceOrientationNED.Roll(), 0, 1e-2);
EXPECT_TRUE((differenceOrientationNED.Pitch() < -0.04));
EXPECT_NEAR(differenceOrientationNED.Yaw(), 0, 1e-2);

// In the sdf world, the IMU model & link have a yaw pose of PI/2,
// which means the initial orientation of DEFAULT IMU is
// effectively WND (by rotating ENU by PI about N). Therefore,
// pitch and yaw for DEFAULT case should be zero, and roll
// should be nontrivial positive value.
EXPECT_TRUE((differenceOrientationDEFAULT_1.Roll() > 0.04));
EXPECT_NEAR(differenceOrientationDEFAULT_1.Pitch(), 0, 1e-2);
EXPECT_NEAR(differenceOrientationDEFAULT_1.Yaw(), 0, 1e-2);

// For DEFAULT_2, model has a pose PI/2 0 0 & link has a pose of
// 0 PI/2 0, which makes the frame NUE.
EXPECT_NEAR(differenceOrientationDEFAULT_2.Roll(), 0, 1e-2);
EXPECT_NEAR(differenceOrientationDEFAULT_2.Pitch(), 0, 1e-2);
EXPECT_TRUE((differenceOrientationDEFAULT_2.Yaw() < -0.04));

// For DEFAULT_3, model has a pose PI/2 0 0 & link has a pose of
// 0 0 PI/2, which makes the frame UWS.
EXPECT_NEAR(differenceOrientationDEFAULT_3.Roll(), 0, 1e-2);
EXPECT_TRUE((differenceOrientationDEFAULT_3.Pitch() > 0.04));
EXPECT_NEAR(differenceOrientationDEFAULT_3.Yaw(), 0, 1e-2);

// Those nontrivial values should match for all sensors.
EXPECT_NEAR(differenceOrientationENU.Roll(),
differenceOrientationNED.Pitch(), 1e-4);

EXPECT_NEAR(differenceOrientationENU.Roll(),
-differenceOrientationDEFAULT_1.Roll(), 1e-4);
EXPECT_NEAR(differenceOrientationENU.Roll(),
differenceOrientationDEFAULT_2.Yaw(), 1e-4);
EXPECT_NEAR(differenceOrientationENU.Roll(),
-differenceOrientationDEFAULT_3.Pitch(), 1e-4);
}
Loading