Skip to content

Commit 5efd8c1

Browse files
authored
Merge pull request EasyNavigation#100 from Butakus/odometry_perception
Add Odometry perception
2 parents f1ae62b + 3c1a6b4 commit 5efd8c1

9 files changed

Lines changed: 277 additions & 7 deletions

File tree

easynav_localizer/include/easynav_localizer/DummyLocalizer.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@ class DummyLocalizer : public easynav::LocalizerMethodBase
6565
virtual void update(NavState & nav_state) override;
6666

6767
private:
68-
/// @brief Internal pose placeholder.
69-
nav_msgs::msg::Odometry robot_pose_;
70-
7168
std::unique_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
7269

7370
double cycle_time_rt_ {0.0};

easynav_localizer/src/easynav_localizer/DummyLocalizer.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ void DummyLocalizer::update_rt([[maybe_unused]] NavState & nav_state)
5050
RTTFBuffer::getInstance()->setTransform(tf_msg, "easynav", false);
5151
// tf_broadcaster_->sendTransform(tf_msg);
5252

53-
nav_state.set("robot_pose", robot_pose_);
54-
5553
// Busy wait to simulate processing time
5654
while (chr::duration<double>(chr::steady_clock::now() - start).count() < cycle_time_rt_) {}
5755
}
@@ -71,8 +69,6 @@ void DummyLocalizer::update([[maybe_unused]] NavState & nav_state)
7169
RTTFBuffer::getInstance()->setTransform(tf_msg, "easynav", false);
7270
// tf_broadcaster_->sendTransform(tf_msg);
7371

74-
nav_state.set("robot_pose", robot_pose_);
75-
7672
// Busy wait to simulate processing time
7773
while (chr::duration<double>(chr::steady_clock::now() - start).count() < cycle_time_nort_) {}
7874
}

easynav_localizer/src/easynav_localizer/LocalizerNode.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include "lifecycle_msgs/msg/transition.hpp"
2121
#include "lifecycle_msgs/msg/state.hpp"
22+
#include "nav_msgs/msg/odometry.hpp"
23+
#include "tf2/LinearMath/Transform.hpp"
2224

2325
#include "easynav_localizer/LocalizerNode.hpp"
2426

@@ -35,6 +37,30 @@ LocalizerNode::LocalizerNode(
3537

3638
localizer_loader_ = std::make_unique<pluginlib::ClassLoader<easynav::LocalizerMethodBase>>(
3739
"easynav_core", "easynav::LocalizerMethodBase");
40+
41+
NavState::register_printer<nav_msgs::msg::Odometry>(
42+
[](const nav_msgs::msg::Odometry & odom) {
43+
const double x = odom.pose.pose.position.x;
44+
const double y = odom.pose.pose.position.y;
45+
const double z = odom.pose.pose.position.z;
46+
47+
const tf2::Quaternion q(
48+
odom.pose.pose.orientation.x,
49+
odom.pose.pose.orientation.y,
50+
odom.pose.pose.orientation.z,
51+
odom.pose.pose.orientation.w);
52+
53+
double roll, pitch, yaw;
54+
tf2::Matrix3x3(q).getRPY(roll, pitch, yaw);
55+
56+
std::ostringstream ret;
57+
ret << std::fixed << std::setprecision(3);
58+
ret << "{" << rclcpp::Time(odom.header.stamp).seconds() << " } Odometry with pose: (x: " <<
59+
x << ", y: " << y << ", z: " << z << ", yaw: " << yaw << ")";
60+
return ret.str();
61+
});
62+
63+
3864
}
3965

4066
LocalizerNode::~LocalizerNode()

easynav_sensors/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ add_library(${PROJECT_NAME} SHARED
4040
src/easynav_sensors/types/PointPerception.cpp
4141
src/easynav_sensors/types/IMUPerception.cpp
4242
src/easynav_sensors/types/GNSSPerception.cpp
43+
src/easynav_sensors/types/OdometryPerception.cpp
4344
src/easynav_sensors/types/DetectionsPerception.cpp
4445
)
4546
target_include_directories(${PROJECT_NAME} PUBLIC

easynav_sensors/easynav_sensors_perception_handler_plugins.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
Perceptions are stored in NavState under the "gnss" group.
2626
</description>
2727
</class>
28+
<class name="easynav_sensors/OdometryPerceptionHandler"
29+
type="easynav::OdometryPerceptionHandler"
30+
base_class_type="easynav::PerceptionHandler">
31+
<description>
32+
Handler for odometry sensors. Supports nav_msgs/msg/Odometry message type.
33+
Perceptions are stored in NavState under the "odom" group.
34+
</description>
35+
</class>
2836
<class name="easynav_sensors/ImagePerceptionHandler"
2937
type="easynav::ImagePerceptionHandler"
3038
base_class_type="easynav::PerceptionHandler">
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2026 Intelligent Robotics Lab
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// \file
16+
/// \brief Defines data structures and utilities for representing and processing odometry perceptions.
17+
///
18+
/// This file contains the definition of the OdometryPerception class, which holds odometry sensor data,
19+
/// and the OdometryPerceptionHandler class, which handles subscriptions to odometry messages and
20+
/// transforms them into OdometryPerception instances. It also defines an alias for a collection of such
21+
/// perceptions.
22+
23+
#ifndef EASYNAV_SENSORS_TYPES__ODOMETRYPERCEPTIONS_HPP_
24+
#define EASYNAV_SENSORS_TYPES__ODOMETRYPERCEPTIONS_HPP_
25+
26+
#include <string_view>
27+
#include <vector>
28+
29+
#include "nav_msgs/msg/odometry.hpp"
30+
31+
#include "easynav_sensors/types/Perceptions.hpp"
32+
33+
namespace easynav
34+
{
35+
36+
/// \class OdometryPerception
37+
/// \brief Represents a single odometry perception from a sensor.
38+
///
39+
/// Inherits from PerceptionBase and stores a nav_msgs::msg::Odometry message.
40+
class OdometryPerception : public PerceptionBase
41+
{
42+
public:
43+
/// \brief Group identifier for odometry perceptions.
44+
static constexpr std::string_view default_group_ = "odom";
45+
46+
/// \brief Returns whether the given ROS 2 type name is supported by this perception.
47+
/// \param t Fully qualified message type name (e.g., "nav_msgs/msg/Odometry").
48+
/// \return true if \p t equals "nav_msgs/msg/Odometry", otherwise false.
49+
static inline bool supports_msg_type(std::string_view t)
50+
{
51+
return t == "nav_msgs/msg/Odometry";
52+
}
53+
54+
OdometryPerception()
55+
{
56+
[[maybe_unused]] static const bool _ = [] {
57+
::easynav::NavState::register_printer<OdometryPerception>(
58+
[](const OdometryPerception & perception) {
59+
std::ostringstream ret;
60+
const auto & pose = perception.data.pose.pose;
61+
const auto & twist = perception.data.twist.twist;
62+
ret << "{ " << perception.stamp.seconds()
63+
<< " } OdometryPerception pose = ("
64+
<< pose.position.x << ", "
65+
<< pose.position.y << ", "
66+
<< pose.position.z << "), twist = ("
67+
<< twist.linear.x << ", "
68+
<< twist.linear.y << ", "
69+
<< twist.linear.z
70+
<< ") in frame [" << perception.frame_id
71+
<< "] with ts " << perception.stamp.seconds() << "\n";
72+
return ret.str();
73+
});
74+
return true;
75+
}();
76+
}
77+
78+
/// \brief Odometry data received from the sensor.
79+
nav_msgs::msg::Odometry data;
80+
};
81+
82+
/// \class OdometryPerceptionHandler
83+
/// \brief Handles the creation and updating of OdometryPerception instances from nav_msgs::msg::Odometry messages.
84+
///
85+
/// This class provides methods to register subscriptions to odometry topics and update OdometryPerception objects.
86+
class OdometryPerceptionHandler : public PerceptionHandler
87+
{
88+
public:
89+
/// \brief Optional post-initialization hook for subclasses.
90+
/// Here, the handler must reserve memory to store the perception data
91+
/// and create any Subscription or similar objects to read the data.
92+
void on_initialize() override;
93+
94+
/// @brief Run one real-time sensor processing cycle.
95+
/// This method is called by the SensorsNode before executing its cycle_rt.
96+
/// Here the handler should update the NavState with the sensor data.
97+
/// If new data arrived before this call and the state is updated, it must return true.
98+
///
99+
/// @param nav_state Pointer to the NavState to store the sensor data.
100+
/// @return True if new data was stored (to trigger processing).
101+
bool cycle_rt([[maybe_unused]] std::shared_ptr<NavState> nav_state) override;
102+
103+
private:
104+
/// \brief pointer to the perception data
105+
std::shared_ptr<OdometryPerception> perception_data_ {nullptr};
106+
107+
/// \brief pointer to the subscription object
108+
rclcpp::SubscriptionBase::SharedPtr perception_sub_;
109+
110+
/// \brief NavState key to store the odometry value (defaults to sensor name)
111+
std::string odom_ns_key_;
112+
};
113+
114+
/**
115+
* @typedef OdometryPerceptions
116+
* @brief Alias for a vector of shared pointers to OdometryPerception objects.
117+
*
118+
* The container can represent a time-ordered or batched collection, depending on producer logic.
119+
*/
120+
using OdometryPerceptions =
121+
std::vector<std::shared_ptr<OdometryPerception>>;
122+
123+
/// @brief Retrieves the latest timestamp among a set of odometry perceptions.
124+
/// @param perceptions Container of odometry perceptions.
125+
/// @return The most recent timestamp found in \p perceptions, or a default-constructed \c rclcpp::Time if \p perceptions is empty.
126+
rclcpp::Time get_latest_odometry_perceptions_stamp(const OdometryPerceptions & perceptions);
127+
128+
} // namespace easynav
129+
130+
#endif // EASYNAV_SENSORS_TYPES__ODOMETRYPERCEPTIONS_HPP_

easynav_sensors/src/easynav_sensors/SensorsNode.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ SensorsNode::SensorsNode(const rclcpp::NodeOptions & options)
6363
{"sensor_msgs/msg/LaserScan", "easynav_sensors/PointPerceptionHandler"},
6464
{"sensor_msgs/msg/Imu", "easynav_sensors/IMUPerceptionHandler"},
6565
{"sensor_msgs/msg/NavSatFix", "easynav_sensors/GNSSPerceptionHandler"},
66+
{"nav_msgs/msg/Odometry", "easynav_sensors/OdometryPerceptionHandler"},
6667
{"sensor_msgs/msg/Image", "easynav_sensors/ImagePerceptionHandler"},
6768
{"vision_msgs/msg/Detection3DArray", "easynav_sensors/DetectionsPerceptionHandler"},
6869
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2026 Intelligent Robotics Lab
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
#include <string>
17+
18+
#include "nav_msgs/msg/odometry.hpp"
19+
20+
#include "rclcpp/time.hpp"
21+
22+
#include "easynav_sensors/types/OdometryPerception.hpp"
23+
24+
namespace easynav
25+
{
26+
27+
void OdometryPerceptionHandler::on_initialize()
28+
{
29+
// Create the perception data instance
30+
perception_data_ = std::make_shared<OdometryPerception>();
31+
32+
// Get sensor parameters
33+
auto node = get_node();
34+
std::string topic, msg_type;
35+
36+
if (!node->has_parameter(get_sensor_name() + ".topic")) {
37+
node->declare_parameter(get_sensor_name() + ".topic", std::string{});
38+
}
39+
if (!node->has_parameter(get_sensor_name() + ".type")) {
40+
node->declare_parameter(get_sensor_name() + ".type", std::string{});
41+
}
42+
if (!node->has_parameter(get_sensor_name() + ".nav_state_key")) {
43+
node->declare_parameter(get_sensor_name() + ".nav_state_key", get_sensor_name());
44+
}
45+
46+
node->get_parameter(get_sensor_name() + ".topic", topic);
47+
node->get_parameter(get_sensor_name() + ".type", msg_type);
48+
node->get_parameter(get_sensor_name() + ".nav_state_key", odom_ns_key_);
49+
50+
// Setup subscription
51+
auto options = rclcpp::SubscriptionOptions();
52+
options.callback_group = get_realtime_cbg();
53+
54+
const auto clock_type = node->get_clock()->get_clock_type();
55+
56+
if (msg_type != "nav_msgs/msg/Odometry") {
57+
throw std::runtime_error("Unsupported message type for OdometryPerceptionHandler: " + msg_type);
58+
}
59+
60+
perception_sub_ = node->create_subscription<nav_msgs::msg::Odometry>(
61+
topic, rclcpp::QoS(1),
62+
[this, clock_type](const nav_msgs::msg::Odometry::SharedPtr msg)
63+
{
64+
perception_data_->stamp = rclcpp::Time(msg->header.stamp, clock_type);
65+
perception_data_->frame_id = msg->header.frame_id;
66+
perception_data_->new_data = true;
67+
perception_data_->data = *msg;
68+
perception_data_->valid = true;
69+
},
70+
options);
71+
}
72+
73+
bool OdometryPerceptionHandler::cycle_rt(std::shared_ptr<NavState> nav_state)
74+
{
75+
// Store the data in the NavState
76+
// NOTE: We store the actual Odometry message, not the Perception object.
77+
// This is so this handler can be a replacement of the localizer plugin.
78+
nav_state->set(odom_ns_key_, perception_data_->data);
79+
// Check if there was new data to trigger process and reset new_data state
80+
const bool should_trigger = perception_data_->new_data;
81+
perception_data_->new_data = false;
82+
return should_trigger;
83+
}
84+
85+
rclcpp::Time get_latest_odometry_perceptions_stamp(const OdometryPerceptions & perceptions)
86+
{
87+
auto is_newer = [](const rclcpp::Time & a, const rclcpp::Time & b) {
88+
if (a.get_clock_type() == b.get_clock_type()) {
89+
return a > b;
90+
}
91+
return a.nanoseconds() > b.nanoseconds();
92+
};
93+
94+
rclcpp::Time latest_stamp;
95+
bool inited = false;
96+
97+
for (const auto & perception : perceptions) {
98+
if (!inited || is_newer(perception->stamp, latest_stamp)) {
99+
latest_stamp = perception->stamp;
100+
inited = true;
101+
}
102+
}
103+
104+
return latest_stamp;
105+
}
106+
107+
} // namespace easynav
108+
109+
#include "pluginlib/class_list_macros.hpp"
110+
PLUGINLIB_EXPORT_CLASS(easynav::OdometryPerceptionHandler, easynav::PerceptionHandler)

easynav_sensors/tests/plugin_tests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static const std::vector<std::string> kAllPlugins = {
5454
"easynav_sensors/PointPerceptionHandler",
5555
"easynav_sensors/IMUPerceptionHandler",
5656
"easynav_sensors/GNSSPerceptionHandler",
57+
"easynav_sensors/OdometryPerceptionHandler",
5758
"easynav_sensors/ImagePerceptionHandler",
5859
"easynav_sensors/DetectionsPerceptionHandler",
5960
};

0 commit comments

Comments
 (0)