Skip to content

Commit 4f7b10c

Browse files
add odometry subscriber
Signed-off-by: fbattocchia <florencia.battochia@creativa77.com.ar>
1 parent 9994687 commit 4f7b10c

9 files changed

Lines changed: 121 additions & 120 deletions

File tree

beluga_amcl/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ See [Beluga AMCL documentation](https://ekumen-os.github.io/beluga/packages/belu
3232

3333
### Subscribed Topics
3434

35-
The subscribed topic names can be changed with the parameters `map_topic`, `scan_topic` and `initial_pose_topic`.
35+
The subscribed topic names can be changed with the parameters `map_topic`, `scan_topic`, `odom_topic` and `initial_pose_topic`.
3636

3737
| Topic | Type | Description |
3838
|------------------|-------------------------------------------|-----------------------------------------------------------------------------|
3939
| `map` | `nav_msgs/OccupancyGrid` | Input topic for map updates. |
4040
| `scan` | `sensor_msgs/LaserScan` | Input topic for laser scan updates. |
41+
| `odom` | `nav_msgs/Odometry` | Input topic for odometry updates (when propagation_rate is enabled). |
4142
| `initial_pose` | `geometry_msgs/PoseWithCovarianceStamped` | Input topic for pose mean and covariance to initialize the particle filter. |
4243

4344
### Published Topics

beluga_amcl/config/Amcl.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ gen.add(
7575
default="scan"
7676
)
7777

78+
gen.add(
79+
"odom_topic", str_t, 0,
80+
"Topic to subscribe to in order to "
81+
"receive the odometry data for localization.",
82+
default="odom"
83+
)
84+
7885
gen.add(
7986
"min_particles", int_t, 0,
8087
"Minimum allowed number of particles.",

beluga_amcl/include/beluga_amcl/amcl_node.hpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040

4141
#include <beluga/beluga.hpp>
4242
#include <beluga_ros/amcl.hpp>
43+
#include <deque>
44+
#include <nav_msgs/msg/odometry.hpp>
4345
#include "beluga_amcl/message_filters.hpp"
4446
#include "beluga_amcl/ros2_common.hpp"
4547

46-
#include <deque>
47-
4848
/**
4949
* \file
5050
* \brief ROS 2 integration of the 2D AMCL algorithm.
@@ -93,8 +93,8 @@ class AmclNode : public BaseAMCLNode {
9393
/// Callback for laser scan updates.
9494
void laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr);
9595

96-
/// Callback for increased propagation timer.
97-
void propagation_timer_callback();
96+
/// Callback for odometry updates.
97+
void odometry_callback(nav_msgs::msg::Odometry::ConstSharedPtr);
9898

9999
/// Helper function to get base pose in odom frame at specific time.
100100
auto get_base_pose_in_odom(const tf2::TimePoint& time) const -> std::optional<Sophus::SE2d>;
@@ -151,8 +151,8 @@ class AmclNode : public BaseAMCLNode {
151151
/// Connection for laser scan updates filter and callback.
152152
::message_filters::Connection laser_scan_connection_;
153153

154-
/// Timer for increased propagation rate.
155-
rclcpp::TimerBase::SharedPtr propagation_timer_;
154+
/// Odometry updates subscription.
155+
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
156156

157157
/// Particle filter instance.
158158
std::unique_ptr<beluga_ros::Amcl> particle_filter_;
@@ -162,10 +162,8 @@ class AmclNode : public BaseAMCLNode {
162162
std::optional<Sophus::SE2d> last_known_odom_transform_in_map_;
163163
/// Whether to broadcast transforms or not.
164164
bool enable_tf_broadcast_{false};
165-
/// Type Buffer for queued odometry motions (timestamp, pose)
166-
using OdometryMotion = std::pair<tf2::TimePoint, Sophus::SE2d>;
167165
/// Buffer for queued odometry motions (timestamp, pose)
168-
std::deque<OdometryMotion> odometry_motion_buffer_;
166+
std::deque<beluga_ros::OdometryMotion> odometry_motion_buffer_;
169167
};
170168

171169
} // namespace beluga_amcl

beluga_amcl/src/amcl_node.cpp

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,8 @@ AmclNode::AmclNode(const rclcpp::NodeOptions& options) : BaseAMCLNode{"amcl", ""
186186

187187
{
188188
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
189-
descriptor.description = "Frequency in Hz for increased propagation rate. Set to 0 to disable.";
190-
descriptor.floating_point_range.resize(1);
191-
descriptor.floating_point_range[0].from_value = 0.0;
192-
descriptor.floating_point_range[0].to_value = 100.0;
193-
descriptor.floating_point_range[0].step = 0.0;
194-
declare_parameter("propagation_rate", rclcpp::ParameterValue(0.0), descriptor);
189+
descriptor.description = "Set true to enable increased propagation, and set false to disable it.";
190+
declare_parameter("propagation_rate", rclcpp::ParameterValue(false), descriptor);
195191
}
196192
}
197193

@@ -262,15 +258,14 @@ void AmclNode::do_activate(const rclcpp_lifecycle::State&) {
262258
common_service_qos, common_callback_group_);
263259
RCLCPP_INFO(get_logger(), "Created request_nomotion_update service");
264260

265-
// Setup increased propagation timer if enabled
266261
{
267-
const double propagation_freq = get_parameter("propagation_rate").as_double();
268-
if (propagation_freq > 0.0) {
269-
auto period = std::chrono::duration<double>(1.0 / propagation_freq);
270-
propagation_timer_ =
271-
create_wall_timer(period, std::bind(&AmclNode::propagation_timer_callback, this), common_callback_group_);
272-
RCLCPP_INFO(get_logger(), "Created propagation timer at %.1f Hz", propagation_freq);
273-
// Initialize odometry motion buffer
262+
// Subscribe to odometry topic to buffer odometry motions
263+
if (get_parameter("propagation_rate").as_bool()) {
264+
odom_sub_ = create_subscription<nav_msgs::msg::Odometry>(
265+
get_parameter("odom_topic").as_string(), rclcpp::SensorDataQoS(),
266+
std::bind(&AmclNode::odometry_callback, this, std::placeholders::_1), common_subscription_options_);
267+
268+
RCLCPP_INFO(get_logger(), "Subscribed to odom_topic: %s", odom_sub_->get_topic_name());
274269
odometry_motion_buffer_.clear();
275270
}
276271
}
@@ -282,7 +277,7 @@ void AmclNode::do_deactivate(const rclcpp_lifecycle::State&) {
282277
laser_scan_filter_.reset();
283278
laser_scan_sub_.reset();
284279
global_localization_server_.reset();
285-
propagation_timer_.reset();
280+
odom_sub_.reset();
286281
if (likelihood_field_pub_) {
287282
likelihood_field_pub_->on_deactivate();
288283
}
@@ -491,38 +486,18 @@ void AmclNode::do_periodic_timer_callback() {
491486
}
492487
}
493488

494-
auto AmclNode::get_base_pose_in_odom(const tf2::TimePoint& time) const -> std::optional<Sophus::SE2d> {
495-
auto base_pose_in_odom = Sophus::SE2d{};
496-
try {
497-
tf2::convert(
498-
tf_buffer_
499-
->lookupTransform(
500-
get_parameter("odom_frame_id").as_string(), get_parameter("base_frame_id").as_string(), time)
501-
.transform,
502-
base_pose_in_odom);
503-
return base_pose_in_odom;
504-
} catch (const tf2::TransformException& error) {
505-
RCLCPP_ERROR(get_logger(), "Could not transform from odom to base: %s", error.what());
506-
return std::nullopt;
507-
}
508-
}
509-
510-
void AmclNode::propagation_timer_callback() {
489+
void AmclNode::odometry_callback(nav_msgs::msg::Odometry::ConstSharedPtr odom) {
511490
if (!particle_filter_) {
512491
RCLCPP_WARN_THROTTLE(
513-
get_logger(), *get_clock(), 2000, "Ignoring propagation because the particle filter has not been initialized");
514-
return;
515-
}
516-
517-
const auto base_pose_in_odom = get_base_pose_in_odom(tf2::TimePointZero);
518-
if (!base_pose_in_odom.has_value()) {
492+
get_logger(), *get_clock(), 2000,
493+
"Ignoring odometry data because the particle filter has not been initialized");
519494
return;
520495
}
521-
522-
const auto now = this->now();
523-
const auto time = tf2_ros::fromMsg(now);
524-
// Queue odometry motion (timestamp, pose) for later processing in laser_callback
525-
odometry_motion_buffer_.emplace_back(time, base_pose_in_odom.value());
496+
// Use the odometry message timestamp and pose
497+
const auto time = tf2_ros::fromMsg(odom->header.stamp);
498+
auto base_pose_in_odom = Sophus::SE2d{};
499+
tf2::convert(odom->pose.pose, base_pose_in_odom);
500+
odometry_motion_buffer_.emplace_back(time, base_pose_in_odom);
526501
}
527502

528503
void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_scan) {
@@ -533,21 +508,23 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
533508
}
534509

535510
// If propagation_rate is enabled, process odometry buffer up to lidar timestamp
536-
if (get_parameter("propagation_rate").as_double() > 0.0) {
511+
if (get_parameter("propagation_rate").as_bool()) {
537512
const auto laser_scan_stamp = tf2_ros::fromMsg(laser_scan->header.stamp);
538-
while (!odometry_motion_buffer_.empty()) {
539-
const auto& [odom_time, odom_pose] = odometry_motion_buffer_.front();
540-
if (odom_time >= laser_scan_stamp) {
541-
break;
542-
}
543-
particle_filter_->update(odom_pose);
544-
odometry_motion_buffer_.pop_front();
545-
}
513+
particle_filter_->process_buffered_odometry_until(odometry_motion_buffer_, laser_scan_stamp);
546514
}
547515

548516
// Get base pose in odom frame at laser scan timestamp
549-
const auto base_pose_in_odom = get_base_pose_in_odom(tf2_ros::fromMsg(laser_scan->header.stamp));
550-
if (!base_pose_in_odom.has_value()) {
517+
auto base_pose_in_odom = Sophus::SE2d{};
518+
try {
519+
tf2::convert(
520+
tf_buffer_
521+
->lookupTransform(
522+
get_parameter("odom_frame_id").as_string(), get_parameter("base_frame_id").as_string(),
523+
tf2_ros::fromMsg(laser_scan->header.stamp))
524+
.transform,
525+
base_pose_in_odom);
526+
} catch (const tf2::TransformException& error) {
527+
RCLCPP_ERROR(get_logger(), "Could not transform from odom to base: %s", error.what());
551528
return;
552529
}
553530

@@ -567,7 +544,7 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
567544

568545
const auto update_start_time = std::chrono::high_resolution_clock::now();
569546
const auto new_estimate = particle_filter_->update(
570-
base_pose_in_odom.value(), //
547+
base_pose_in_odom, //
571548
beluga_ros::LaserScan{
572549
laser_scan,
573550
laser_pose_in_base,
@@ -580,7 +557,7 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
580557

581558
if (new_estimate.has_value()) {
582559
const auto& [base_pose_in_map, _] = new_estimate.value();
583-
last_known_odom_transform_in_map_ = base_pose_in_map * base_pose_in_odom.value().inverse();
560+
last_known_odom_transform_in_map_ = base_pose_in_map * base_pose_in_odom.inverse();
584561
last_known_estimate_ = new_estimate;
585562

586563
RCLCPP_INFO(

beluga_amcl/src/ros2_common.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ BaseAMCLNode::BaseAMCLNode(
6666
this->declare_parameter("scan_topic", rclcpp::ParameterValue("scan"), descriptor);
6767
}
6868

69+
{
70+
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
71+
descriptor.description = "Topic to subscribe to in order to receive odometry messages for motion propagation.";
72+
this->declare_parameter("odom_topic", rclcpp::ParameterValue("odom"), descriptor);
73+
}
74+
6975
{
7076
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
7177
descriptor.description = "Minimum allowed number of particles.";

beluga_amcl/test/test_amcl_node.cpp

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ class AmclNodeUnderTest : public beluga_amcl::AmclNode {
4646
/// Return the last known estimate. Throws if there is no estimate.
4747
const auto& estimate() { return last_known_estimate_.value(); }
4848

49-
/// Check if propagation timer is created
50-
bool has_propagation_timer() const { return propagation_timer_ != nullptr; }
49+
/// Check if odom_sub_ is created
50+
bool has_odom_sub() const { return odom_sub_ != nullptr; }
5151

52-
/// Expose propagation timer callback for testing
53-
void propagation_timer_callback() { AmclNode::propagation_timer_callback(); }
52+
/// Expose odometry callback for testing
53+
void odometry_callback(nav_msgs::msg::Odometry::ConstSharedPtr odom) { AmclNode::odometry_callback(odom); }
5454

5555
/// Expose odometry_motion_buffer_ for testing
5656
const auto& odometry_motion_buffer() const { return odometry_motion_buffer_; }
@@ -707,44 +707,44 @@ TEST_F(TestNode, TransformValue) {
707707
EXPECT_NEAR(transform.so2().log(), 0.0, 0.01);
708708
}
709709

710-
TEST_F(TestNode, PropagationTimerNotCreatedWhenDisabled) {
711-
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 0.0});
710+
TEST_F(TestNode, OdomSubNotCreatedWhenPropagationDisabled) {
711+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", false});
712712
amcl_node_->configure();
713713
amcl_node_->activate();
714714
tester_node_->publish_map();
715715
ASSERT_TRUE(wait_for_initialization());
716716

717-
// Timer should not be created when frequency is 0
718-
EXPECT_FALSE(amcl_node_->has_propagation_timer());
717+
// odom_sub_ should not be created when propagation is disabled
718+
EXPECT_EQ(amcl_node_->has_odom_sub(), false);
719719
}
720720

721-
TEST_F(TestNode, PropagationTimerCreatedWhenEnabled) {
722-
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 10.0});
721+
TEST_F(TestNode, OdomSubCreatedWhenPropagationEnabled) {
722+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", true});
723723
amcl_node_->configure();
724724
amcl_node_->activate();
725725
tester_node_->publish_map();
726726
ASSERT_TRUE(wait_for_initialization());
727727

728-
// Timer should be created when frequency > 0
729-
EXPECT_TRUE(amcl_node_->has_propagation_timer());
728+
// odom_sub_ should be created when propagation is enabled
729+
EXPECT_EQ(amcl_node_->has_odom_sub(), true);
730730
}
731731

732-
TEST_F(TestNode, PropagationTimerIntegrationTest) {
733-
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 5.0});
732+
TEST_F(TestNode, PropagationRate) {
733+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", true});
734734
amcl_node_->set_parameter(rclcpp::Parameter{"set_initial_pose", true});
735735
amcl_node_->configure();
736736
amcl_node_->activate();
737737
tester_node_->publish_map();
738738
ASSERT_TRUE(wait_for_initialization());
739739

740-
tester_node_->publish_odom_to_base_tf(Sophus::SE2d{});
741-
742-
// Wait for several timer executions to fill the odometry buffer
743-
spin_for(500ms, amcl_node_, tester_node_);
740+
// Publish several odometry messages to fill the buffer
741+
for (int i = 0; i < 5; ++i) {
742+
tester_node_->publish_odometry(i * 0.1, 0.0);
743+
spin_for(20ms, amcl_node_, tester_node_);
744+
}
744745

745746
// Check that odometry_motion_buffer_ has values before laser scan
746-
EXPECT_FALSE(amcl_node_->odometry_motion_buffer().empty());
747-
const auto buffer_size_before = amcl_node_->odometry_motion_buffer().size();
747+
EXPECT_EQ(amcl_node_->odometry_motion_buffer().size(), 5);
748748

749749
// Configure TF so get_base_pose_in_odom works by publishing a laser scan with transform
750750
tester_node_->publish_laser_scan_with_odom_to_base(Sophus::SE2d{});
@@ -753,38 +753,20 @@ TEST_F(TestNode, PropagationTimerIntegrationTest) {
753753
spin_for(100ms, amcl_node_, tester_node_);
754754

755755
// Check that odometry_motion_buffer_ was consumed up to the lidar timestamp
756-
EXPECT_LT(amcl_node_->odometry_motion_buffer().size(), buffer_size_before);
756+
EXPECT_EQ(amcl_node_->odometry_motion_buffer().size(), 0);
757757

758758
// Verify particle filter still exists and has particles
759759
EXPECT_TRUE(amcl_node_->particle_filter() != nullptr);
760760
EXPECT_GT(amcl_node_->particle_filter()->particles().size(), 0UL);
761761
}
762762

763-
TEST_F(TestNode, PropagationTimerWithoutParticles) {
763+
TEST_F(TestNode, OdomSubWithoutParticles) {
764764
// Verify that particle filter remains uninitialized
765765
EXPECT_FALSE(amcl_node_->particle_filter() != nullptr);
766766

767-
// Call propagation timer callback when particle filter is not initialized
768-
amcl_node_->propagation_timer_callback();
769-
770-
// The callback should have returned early without filling the queue
771-
EXPECT_TRUE(amcl_node_->odometry_motion_buffer().empty());
772-
}
773-
774-
TEST_F(TestNode, PropagationTimerWithoutBaseToOdom) {
775-
amcl_node_->set_parameter(rclcpp::Parameter{"set_initial_pose", true});
776-
amcl_node_->configure();
777-
amcl_node_->activate();
778-
tester_node_->publish_map();
779-
ASSERT_TRUE(wait_for_initialization());
780-
781-
// Particle filter is initialized but no odom->base transform is available
782-
EXPECT_TRUE(amcl_node_->is_initialized());
783-
EXPECT_TRUE(amcl_node_->particle_filter() != nullptr);
784-
785-
// Call propagation callback without any transform data
786-
// This should return early because get_base_pose_in_odom() will fail
787-
amcl_node_->propagation_timer_callback();
767+
// Call odometry callback when particle filter is not initialized
768+
const nav_msgs::msg::Odometry::SharedPtr odom = std::make_shared<nav_msgs::msg::Odometry>();
769+
amcl_node_->odometry_callback(odom);
788770

789771
// The callback should have returned early without filling the queue
790772
EXPECT_TRUE(amcl_node_->odometry_motion_buffer().empty());

0 commit comments

Comments
 (0)