@@ -183,6 +183,16 @@ AmclNode::AmclNode(const rclcpp::NodeOptions& options) : BaseAMCLNode{"amcl", ""
183183 " and ignore subsequent ones." ;
184184 declare_parameter (" first_map_only" , false , descriptor);
185185 }
186+
187+ {
188+ 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 (" increase_propagation" , rclcpp::ParameterValue (0.0 ), descriptor);
195+ }
186196}
187197
188198AmclNode::~AmclNode () {
@@ -251,6 +261,17 @@ void AmclNode::do_activate(const rclcpp_lifecycle::State&) {
251261 std::placeholders::_3),
252262 common_service_qos, common_callback_group_);
253263 RCLCPP_INFO (get_logger (), " Created request_nomotion_update service" );
264+
265+ // Setup increased propagation timer if enabled
266+ {
267+ const double propagation_freq = get_parameter (" increase_propagation" ).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+ }
274+ }
254275}
255276
256277void AmclNode::do_deactivate (const rclcpp_lifecycle::State&) {
@@ -259,6 +280,7 @@ void AmclNode::do_deactivate(const rclcpp_lifecycle::State&) {
259280 laser_scan_filter_.reset ();
260281 laser_scan_sub_.reset ();
261282 global_localization_server_.reset ();
283+ propagation_timer_.reset ();
262284 if (likelihood_field_pub_) {
263285 likelihood_field_pub_->on_deactivate ();
264286 }
@@ -466,26 +488,51 @@ void AmclNode::do_periodic_timer_callback() {
466488 }
467489}
468490
469- void AmclNode::laser_callback (sensor_msgs::msg::LaserScan::ConstSharedPtr laser_scan) {
470- if (!particle_filter_) {
471- RCLCPP_WARN_THROTTLE (
472- get_logger (), *get_clock (), 2000 , " Ignoring laser data because the particle filter has not been initialized" );
473- return ;
474- }
475-
491+ auto AmclNode::get_base_pose_in_odom (const tf2::TimePoint& time) const -> std::optional<Sophus::SE2d> {
476492 auto base_pose_in_odom = Sophus::SE2d{};
477493 try {
478- // Use the lookupTransform overload with no timeout since we're not using a dedicated
479- // tf thread. The message filter we are using avoids the need for it.
480494 tf2::convert (
481495 tf_buffer_
482496 ->lookupTransform (
483- get_parameter (" odom_frame_id" ).as_string (), get_parameter (" base_frame_id" ).as_string (),
484- tf2_ros::fromMsg (laser_scan->header .stamp ))
497+ get_parameter (" odom_frame_id" ).as_string (), get_parameter (" base_frame_id" ).as_string (), time)
485498 .transform ,
486499 base_pose_in_odom);
500+ return base_pose_in_odom;
487501 } catch (const tf2::TransformException& error) {
488- RCLCPP_ERROR (get_logger (), " Could not transform from odom to base: %s" , error.what ());
502+ RCLCPP_WARN (get_logger (), " Could not get base pose in odom: %s" , error.what ());
503+ return std::nullopt ;
504+ }
505+ }
506+
507+ void AmclNode::propagation_timer_callback () {
508+ if (!particle_filter_) {
509+ RCLCPP_WARN_THROTTLE (
510+ get_logger (), *get_clock (), 2000 , " Ignoring propagation because the particle filter has not been initialized" );
511+ return ;
512+ }
513+
514+ // Get current base pose in odom frame (latest available)
515+ auto base_pose_in_odom = get_base_pose_in_odom (tf2::TimePointZero);
516+ if (!base_pose_in_odom.has_value ()) {
517+ return ;
518+ }
519+
520+ // Force a propagation-only update (without sensor data)
521+ particle_filter_->update_propagation (base_pose_in_odom.value ());
522+
523+ RCLCPP_INFO (get_logger (), " Forced propagation update executed" );
524+ }
525+
526+ void AmclNode::laser_callback (sensor_msgs::msg::LaserScan::ConstSharedPtr laser_scan) {
527+ if (!particle_filter_) {
528+ RCLCPP_WARN_THROTTLE (
529+ get_logger (), *get_clock (), 2000 , " Ignoring laser data because the particle filter has not been initialized" );
530+ return ;
531+ }
532+
533+ // Get base pose in odom frame at laser scan timestamp
534+ auto base_pose_in_odom = get_base_pose_in_odom (tf2_ros::fromMsg (laser_scan->header .stamp ));
535+ if (!base_pose_in_odom.has_value ()) {
489536 return ;
490537 }
491538
@@ -505,7 +552,7 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
505552
506553 const auto update_start_time = std::chrono::high_resolution_clock::now ();
507554 const auto new_estimate = particle_filter_->update (
508- base_pose_in_odom, //
555+ base_pose_in_odom. value () , //
509556 beluga_ros::LaserScan{
510557 laser_scan,
511558 laser_pose_in_base,
@@ -518,7 +565,7 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
518565
519566 if (new_estimate.has_value ()) {
520567 const auto & [base_pose_in_map, _] = new_estimate.value ();
521- last_known_odom_transform_in_map_ = base_pose_in_map * base_pose_in_odom.inverse ();
568+ last_known_odom_transform_in_map_ = base_pose_in_map * base_pose_in_odom.value (). inverse ();
522569 last_known_estimate_ = new_estimate;
523570
524571 RCLCPP_INFO (
0 commit comments