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

IMU sensor API to get world ref frame and heading offset #186

Merged
merged 24 commits into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
17ca1ac
Added basic logic to parse localization frames
adityapande-1995 Jan 11, 2022
d4aa659
Minor cleanup
adityapande-1995 Jan 11, 2022
c479887
Added transformation quaternions to convert frames
adityapande-1995 Jan 11, 2022
1fdc240
Added enum type, review changes
adityapande-1995 Jan 13, 2022
eaa94cd
codecheck fixes
adityapande-1995 Jan 14, 2022
59d0b08
Added testcases
adityapande-1995 Jan 14, 2022
d09f508
Cleanup for codecheck
adityapande-1995 Jan 14, 2022
324ed90
Merge branch 'ign-sensors6' into aditya/named_frames_imu
adityapande-1995 Jan 14, 2022
7bdfb0b
Localization tag parsing moved to a separate method
adityapande-1995 Jan 19, 2022
e16ab67
Changed variable names and docstrings
adityapande-1995 Jan 20, 2022
2e5e910
static const transformTable added
adityapande-1995 Jan 24, 2022
df783de
Added documentation for WorlfFrameEnum
adityapande-1995 Jan 26, 2022
3ead9f2
Address not mapped to object error
adityapande-1995 Jan 27, 2022
0f99a51
Removed debug prints
adityapande-1995 Feb 2, 2022
368a24f
Intialize sensorOrientationRelativeTo
adityapande-1995 Feb 2, 2022
00e9c7c
Placed docstring with enum values
adityapande-1995 Feb 3, 2022
18b3645
Linter fix
adityapande-1995 Feb 3, 2022
33a79c6
Account for heading with CUSTOM refernce frame
adityapande-1995 Feb 4, 2022
8f03ce1
Fixed typos, renamed helper function
adityapande-1995 Feb 14, 2022
654ceb8
Refactored tests
adityapande-1995 Feb 14, 2022
90f6917
Added test case: empty localization tag
adityapande-1995 Mar 1, 2022
3f42ba6
Added invalid localization tag test case
adityapande-1995 Mar 3, 2022
735a61f
changed variable names to lowerCamelCase
adityapande-1995 Mar 3, 2022
744d0d1
Merge branch 'ign-sensors6' into aditya/named_frames_imu
adityapande-1995 Mar 3, 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
26 changes: 26 additions & 0 deletions include/ignition/sensors/ImuSensor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ namespace ignition
{
// Inline bracket to help doxygen filtering.
inline namespace IGNITION_SENSORS_VERSION_NAMESPACE {
//
// \brief Reference frames enum
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
enum class IGNITION_SENSORS_VISIBLE WorldFrameEnumType
{
NONE = 0,
scpeters marked this conversation as resolved.
Show resolved Hide resolved
ENU = 1,
NED = 2,
NWU = 3,
CUSTOM = 4
};

//
/// \brief forward declarations
class ImuSensorPrivate;
Expand Down Expand Up @@ -136,6 +147,21 @@ namespace ignition
/// \return Gravity vectory in meters per second squared.
public: math::Vector3d Gravity() const;

/// \brief Specify the rotation offset of the coordinates of the World
// frame relative to a geo-referenced frame
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
/// \param[in] _rot rotation offset
/// \param[in] _relativeTo world frame orintation, ENU by default
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
public: void SetWorldFrameOrientation(
const math::Quaterniond &_rot, WorldFrameEnumType _relativeTo);

// \brief Read the localization sdf tag
// \param[in] _sdf Imu sdf data as sdf::Sensor
public: void ReadLocalizationTag(const sdf::Sensor &_sdf);

// \brief Read the localization sdf tag
// \param[in] _sdf Imu sdf data as sdf:;ElementPtr
public: void ReadLocalizationTag(sdf::ElementPtr _sdf);

IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING
/// \brief Data pointer for private data
/// \internal
Expand Down
121 changes: 104 additions & 17 deletions src/ImuSensor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ class ignition::sensors::ImuSensorPrivate
/// \brief Flag for if time has been initialized
public: bool timeInitialized = false;

/// \brief Store world heading offset
public: ignition::math::Quaterniond headingOffset;
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved

/// \brief Store world orientation
public: WorldFrameEnumType worldFrameType = WorldFrameEnumType::ENU;
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved

/// \brief Store localization orientation
public: WorldFrameEnumType localizationEnum = WorldFrameEnumType::NONE;
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved

/// \brief Previous update time step.
public: std::chrono::steady_clock::duration prevStep
{std::chrono::steady_clock::duration::zero()};
Expand Down Expand Up @@ -116,23 +125,6 @@ bool ImuSensor::Load(const sdf::Sensor &_sdf)
return false;
}

// Set orientation reference frame
// TODO(adityapande-1995) : Add support for named frames like
// ENU using ign-gazebo
if (_sdf.ImuSensor()->Localization() == "CUSTOM")
{
if (_sdf.ImuSensor()->CustomRpyParentFrame() == "")
{
this->SetOrientationReference(ignition::math::Quaterniond(
_sdf.ImuSensor()->CustomRpy()));
}
else
{
ignwarn << "custom_rpy parent frame must be set to empty "
"string. Setting it to any other frame is not "
"supported yet." << std::endl;
}
}

if (this->Topic().empty())
this->SetTopic("/imu");
Expand Down Expand Up @@ -292,6 +284,101 @@ math::Pose3d ImuSensor::WorldPose() const
return this->dataPtr->worldPose;
}

//////////////////////////////////////////////////
void ImuSensor::ReadLocalizationTag(const sdf::Sensor &_sdf)
{
std::string localization = _sdf.ImuSensor()->Localization();

if (localization == "ENU")
{
this->dataPtr->localizationEnum = WorldFrameEnumType::ENU;
} else if (localization == "NED") {
this->dataPtr->localizationEnum = WorldFrameEnumType::NED;
} else if (localization == "NWU") {
this->dataPtr->localizationEnum = WorldFrameEnumType::NWU;
} else if (localization == "CUSTOM") {
this->dataPtr->localizationEnum = WorldFrameEnumType::CUSTOM;
} else {
this->dataPtr->localizationEnum = WorldFrameEnumType::NONE;
}

// Set orientation reference frame if custom_rpy was supplied
if (this->dataPtr->localizationEnum == WorldFrameEnumType::CUSTOM)
{
if (_sdf.ImuSensor()->CustomRpyParentFrame() == "")
{
this->SetOrientationReference(ignition::math::Quaterniond(
_sdf.ImuSensor()->CustomRpy()));
}
else
{
ignwarn << "custom_rpy parent frame must be set to empty "
"string. Setting it to any other frame is not "
"supported yet." << std::endl;
}
}
}


//////////////////////////////////////////////////
void ImuSensor::ReadLocalizationTag(sdf::ElementPtr _sdf)
{
sdf::Sensor sdfSensor;
sdfSensor.Load(_sdf);
return this->ReadLocalizationTag(sdfSensor);
}

//////////////////////////////////////////////////
void ImuSensor::SetWorldFrameOrientation(
const math::Quaterniond &_rot, WorldFrameEnumType _relativeTo)
{
this->dataPtr->headingOffset = _rot;
this->dataPtr->worldFrameType = _relativeTo;

// Table to hold frame transformations
std::map<WorldFrameEnumType,
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
std::map<WorldFrameEnumType, ignition::math::Quaterniond>>
transformTable =
{
{WorldFrameEnumType::ENU,
{
{WorldFrameEnumType::ENU, ignition::math::Quaterniond(0, 0, 0)},
{WorldFrameEnumType::NED, ignition::math::Quaterniond(
IGN_PI, 0, IGN_PI/2)},
{WorldFrameEnumType::NWU, ignition::math::Quaterniond(
0, 0, IGN_PI/2)},
}
},
{WorldFrameEnumType::NED,
{
{WorldFrameEnumType::ENU, ignition::math::Quaterniond(
IGN_PI, 0, IGN_PI/2).Inverse()},
{WorldFrameEnumType::NED, ignition::math::Quaterniond(0, 0, 0)},
{WorldFrameEnumType::NWU, ignition::math::Quaterniond(
-IGN_PI, 0, 0)},
}
},
{WorldFrameEnumType::NWU,
{
{WorldFrameEnumType::ENU, ignition::math::Quaterniond(
0, 0, -IGN_PI/2)},
{WorldFrameEnumType::NED, ignition::math::Quaterniond(IGN_PI, 0, 0)},
{WorldFrameEnumType::NWU, ignition::math::Quaterniond(0, 0, 0)},
}
}
};

if (this->dataPtr->localizationEnum != WorldFrameEnumType::NONE &&
this->dataPtr->localizationEnum != WorldFrameEnumType::CUSTOM)
{
// A valid named localization tag is supplied in the sdf
auto tranformation =
transformTable[this->dataPtr->worldFrameType][
this->dataPtr->localizationEnum];
this->SetOrientationReference(this->dataPtr->headingOffset * tranformation);
}
}

//////////////////////////////////////////////////
void ImuSensor::SetOrientationReference(const math::Quaterniond &_orient)
{
Expand Down
175 changes: 175 additions & 0 deletions src/ImuSensor_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ TEST(ImuSensor_TEST, OrientationReference)
// Make sure the above dynamic cast worked.
ASSERT_NE(nullptr, sensor);

sensor->ReadLocalizationTag(imuSDF);

sensor->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));

Expand Down Expand Up @@ -499,6 +501,8 @@ TEST(ImuSensor_TEST, CustomRpyParentFrame)
// Make sure the above dynamic cast worked.
ASSERT_NE(nullptr, sensor);

sensor->ReadLocalizationTag(imuSDF);

sensor->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));

Expand All @@ -511,6 +515,177 @@ TEST(ImuSensor_TEST, CustomRpyParentFrame)
EXPECT_EQ(defultOrientValue, sensor->Orientation());
}

sdf::ElementPtr generateSensor(std::string namedFrame)
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved
{
std::ostringstream stream;
stream
<< "<?xml version='1.0'?>"
<< "<sdf version='1.6'>"
<< " <model name='m1'>"
<< " <link name='link1'>"
<< " <sensor name='imu_sensor' type='imu'>"
<< " <topic>imu_test</topic>"
<< " <update_rate>1</update_rate>"
<< " <imu>"
<< " <orientation_reference_frame>"
<< " <localization>"+namedFrame+"</localization>"
<< " </orientation_reference_frame>"
<< " </imu>"
<< " <always_on>1</always_on>"
<< " <visualize>true</visualize>"
<< " </sensor>"
<< " </link>"
<< " </model>"
<< "</sdf>";

sdf::SDFPtr sdfParsed(new sdf::SDF());
sdf::init(sdfParsed);
if (!sdf::readString(stream.str(), sdfParsed))
{
return sdf::ElementPtr();
}
else
{
return sdfParsed->Root()->GetElement("model")->GetElement("link")
->GetElement("sensor");
}
}

//////////////////////////////////////////////////
TEST(ImuSensor_TEST, NamedFrameOrientationReference)
{
// Create a sensor manager
ignition::sensors::Manager mgr;

math::Quaterniond orientValue;

// A. Localization tag is set to ENU
// ---------------------------------
auto sensorENU = mgr.CreateSensor<ignition::sensors::ImuSensor>(
generateSensor("ENU"));
ASSERT_NE(nullptr, sensorENU);
sensorENU->ReadLocalizationTag(generateSensor("ENU"));

// Case A.1 : Localization ref: ENU, World : ENU
// Sensor aligns with ENU, we're measuring wrt ENU
sensorENU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::ENU);
sensorENU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, 0));
EXPECT_EQ(orientValue, sensorENU->Orientation());
adityapande-1995 marked this conversation as resolved.
Show resolved Hide resolved

// Case A.2 Localization ref: ENU, World : ENU + rotation offset
sensorENU->SetWorldFrameOrientation(math::Quaterniond(0, 0, IGN_PI/4),
sensors::WorldFrameEnumType::ENU);
sensorENU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, -IGN_PI/4));
EXPECT_EQ(orientValue, sensorENU->Orientation());

// Case A.3 Localization ref: ENU, World : NWU
// Sensor aligns with NWU, we're measuring wrt ENU
sensorENU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NWU);
sensorENU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, IGN_PI/2));
EXPECT_EQ(orientValue, sensorENU->Orientation());

// Case A.4 Localization ref: ENU, World : NED
// Sensor aligns with NED, we're measuring wrt ENU
sensorENU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NED);
sensorENU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(IGN_PI, 0, IGN_PI/2));
EXPECT_EQ(orientValue, sensorENU->Orientation());

// B. Localization tag is set to NWU
// ---------------------------------
auto sensorNWU = mgr.CreateSensor<ignition::sensors::ImuSensor>(
generateSensor("NWU"));
ASSERT_NE(nullptr, sensorNWU);
sensorNWU->ReadLocalizationTag(generateSensor("NWU"));

// Case B.1 : Localization ref: NWU, World : NWU
// Sensor aligns with NWU, we're measuring wrt NWU
sensorNWU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NWU);
sensorNWU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, 0));
EXPECT_EQ(orientValue, sensorNWU->Orientation());

// Case B.2 : Localization ref: NWU, World : NWU + rotation offset
sensorNWU->SetWorldFrameOrientation(math::Quaterniond(0, 0, IGN_PI/4),
sensors::WorldFrameEnumType::NWU);
sensorNWU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, -IGN_PI/4));
EXPECT_EQ(orientValue, sensorNWU->Orientation());

// Case B.3 : Localization ref: NWU, World : ENU
// Sensor aligns with ENU, we're measuring wrt NWU
sensorNWU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::ENU);
sensorNWU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, -IGN_PI/2));
EXPECT_EQ(orientValue, sensorNWU->Orientation());

// Case B.4 : Localization ref: NWU, World : NED
// Sensor aligns with NED, we're measuring wrt NWU
sensorNWU->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NED);
sensorNWU->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(IGN_PI, 0, 0));
EXPECT_EQ(orientValue, sensorNWU->Orientation());

// C. Localization tag is set to NED
// ---------------------------------
auto sensorNED = mgr.CreateSensor<ignition::sensors::ImuSensor>(
generateSensor("NED"));
ASSERT_NE(nullptr, sensorNED);
sensorNED->ReadLocalizationTag(generateSensor("NED"));

// Case C.1 : Localization ref: NED, World : NED
// Sensor aligns with NED, we're measuring wrt NED
sensorNED->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NED);
sensorNED->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, 0));
EXPECT_EQ(orientValue, sensorNED->Orientation());

// Case C.2 : Localization ref: NED, World : NED + rotation offset
sensorNED->SetWorldFrameOrientation(math::Quaterniond(0, 0, IGN_PI/4),
sensors::WorldFrameEnumType::NED);
sensorNED->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(0, 0, -IGN_PI/4));
EXPECT_EQ(orientValue, sensorNED->Orientation());

// Case C.3 : Localization ref: NED, World : NWU
// Sensor aligns with NWU, we're measuring wrt NED
sensorNED->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::NWU);
sensorNED->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(-IGN_PI, 0, 0));
EXPECT_EQ(orientValue, sensorNED->Orientation());

// Case C.4 : Localization ref: NED, World : ENU
// Sensor aligns with ENU, we're measuring wrt NED
sensorNED->SetWorldFrameOrientation(math::Quaterniond(0, 0, 0),
sensors::WorldFrameEnumType::ENU);
sensorNED->Update(std::chrono::steady_clock::duration(
std::chrono::nanoseconds(10000000)));
orientValue = math::Quaterniond(math::Vector3d(-IGN_PI, 0, IGN_PI/2));
EXPECT_EQ(orientValue, sensorNED->Orientation());
}

//////////////////////////////////////////////////
int main(int argc, char **argv)
{
Expand Down