Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
amcl_node.cpp
1 /*
2  * Copyright (c) 2008, Willow Garage, Inc.
3  * All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  *
19  */
20 
21 /* Author: Brian Gerkey */
22 
23 #include "nav2_amcl/amcl_node.hpp"
24 
25 #include <algorithm>
26 #include <cstdint>
27 #include <cstdio>
28 #include <ctime>
29 #include <iomanip>
30 #include <memory>
31 #include <string>
32 #include <utility>
33 #include <vector>
34 
35 #include "nav2_amcl/angleutils.hpp"
36 #include "nav2_util/geometry_utils.hpp"
37 #include "nav2_amcl/pf/pf.hpp"
38 #include "nav2_util/string_utils.hpp"
39 #include "nav2_amcl/sensors/laser/laser.hpp"
40 #include "rclcpp/node_options.hpp"
41 #include "tf2/convert.hpp"
42 #include "tf2/utils.hpp"
43 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
44 #include "tf2/LinearMath/Transform.hpp"
45 #include "nav2_ros_common/tf2_factories.hpp"
46 
47 #include "nav2_amcl/portable_utils.hpp"
48 #include "nav2_ros_common/validate_messages.hpp"
49 
50 using rcl_interfaces::msg::ParameterType;
51 using namespace std::chrono_literals;
52 
53 namespace nav2_amcl
54 {
55 using nav2_util::geometry_utils::orientationAroundZAxis;
56 
57 AmclNode::AmclNode(const rclcpp::NodeOptions & options)
58 : nav2::LifecycleNode("amcl", "", options)
59 {
60  RCLCPP_INFO(get_logger(), "Creating");
61  init_pose_[0] = 0.0;
62  init_pose_[1] = 0.0;
63  init_pose_[2] = 0.0;
64  init_cov_[0] = 0.0;
65  init_cov_[1] = 0.0;
66  init_cov_[2] = 0.0;
67 }
68 
69 AmclNode::~AmclNode()
70 {
71 }
72 
73 nav2::CallbackReturn
74 AmclNode::on_configure(const rclcpp_lifecycle::State & /*state*/)
75 {
76  RCLCPP_INFO(get_logger(), "Configuring");
77  callback_group_ = create_callback_group(
78  rclcpp::CallbackGroupType::MutuallyExclusive, false);
79  initParameters();
80  initTransforms();
81  initParticleFilter();
82  initLaserScan();
83  initMessageFilters();
84  initPubSub();
85  initServices();
86  initOdometry();
87  executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
88  executor_->add_callback_group(callback_group_, get_node_base_interface());
89  executor_thread_ = std::make_unique<nav2::NodeThread>(executor_);
90  return nav2::CallbackReturn::SUCCESS;
91 }
92 
93 nav2::CallbackReturn
94 AmclNode::on_activate(const rclcpp_lifecycle::State & /*state*/)
95 {
96  RCLCPP_INFO(get_logger(), "Activating");
97 
98  // Lifecycle publishers must be explicitly activated
99  pose_pub_->on_activate();
100  particle_cloud_pub_->on_activate();
101 
102  first_pose_sent_ = false;
103 
104  // Keep track of whether we're in the active state. We won't
105  // process incoming callbacks until we are
106  active_ = true;
107 
108  if (set_initial_pose_) {
109  // ROS parameters take priority over saved pose file
110  if (initialize_at_saved_pose_) {
111  std::ifstream file(saved_pose_filepath_);
112  if (file.is_open()) {
113  RCLCPP_WARN(
114  get_logger(),
115  "Both initial_pose parameters and saved pose file exist. Using ROS parameters.");
116  file.close();
117  }
118  }
119  auto msg = std::make_shared<geometry_msgs::msg::PoseWithCovarianceStamped>();
120 
121  msg->header.stamp = now();
122  msg->header.frame_id = global_frame_id_;
123  msg->pose.pose.position.x = initial_pose_x_;
124  msg->pose.pose.position.y = initial_pose_y_;
125  msg->pose.pose.position.z = initial_pose_z_;
126  msg->pose.pose.orientation = orientationAroundZAxis(initial_pose_yaw_);
127 
128  initialPoseReceived(msg);
129  } else if (initialize_at_saved_pose_) {
130  geometry_msgs::msg::PoseWithCovarianceStamped saved_pose;
131  if (loadPoseFromFile(saved_pose)) {
132  auto msg = std::make_shared<geometry_msgs::msg::PoseWithCovarianceStamped>(saved_pose);
133  initialPoseReceived(msg);
134  } else {
135  RCLCPP_WARN(
136  get_logger(),
137  "initialize_at_saved_pose is true but no saved pose file found at: %s",
138  saved_pose_filepath_.c_str());
139  return nav2::CallbackReturn::FAILURE;
140  }
141  } else if (init_pose_received_on_inactive) {
142  handleInitialPose(last_published_pose_);
143  }
144 
145  // Create pose save timer if save_pose_rate > 0
146  if (save_pose_rate_ > 0.0) {
147  save_pose_timer_ = this->create_timer(
148  std::chrono::duration<double>(1.0 / save_pose_rate_),
149  std::bind(&AmclNode::savePoseTimerCallback, this));
150  }
151 
152  auto node = shared_from_this();
153  // Add callback for dynamic parameters
154  post_set_params_handler_ = node->add_post_set_parameters_callback(
155  std::bind(
156  &AmclNode::updateParametersCallback,
157  this, std::placeholders::_1));
158  on_set_params_handler_ = node->add_on_set_parameters_callback(
159  std::bind(
160  &AmclNode::validateParameterUpdatesCallback,
161  this, std::placeholders::_1));
162 
163  // create bond connection
164  createBond();
165 
166  return nav2::CallbackReturn::SUCCESS;
167 }
168 
169 nav2::CallbackReturn
170 AmclNode::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
171 {
172  RCLCPP_INFO(get_logger(), "Deactivating");
173 
174  active_ = false;
175 
176  // Lifecycle publishers must be explicitly deactivated
177  pose_pub_->on_deactivate();
178  particle_cloud_pub_->on_deactivate();
179 
180  // Stop pose save timer
181  if (save_pose_timer_) {
182  save_pose_timer_->cancel();
183  save_pose_timer_.reset();
184  }
185 
186  // shutdown and reset dynamic parameter handler
187  remove_post_set_parameters_callback(post_set_params_handler_.get());
188  post_set_params_handler_.reset();
189  remove_on_set_parameters_callback(on_set_params_handler_.get());
190  on_set_params_handler_.reset();
191 
192  // destroy bond connection
193  destroyBond();
194 
195  return nav2::CallbackReturn::SUCCESS;
196 }
197 
198 nav2::CallbackReturn
199 AmclNode::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
200 {
201  RCLCPP_INFO(get_logger(), "Cleaning up");
202 
203  executor_thread_.reset();
204 
205  // Get rid of the inputs first (services and message filter input), so we
206  // don't continue to process incoming messages
207  global_loc_srv_.reset();
208  initial_guess_srv_.reset();
209  nomotion_update_srv_.reset();
210  initial_pose_sub_.reset();
211  laser_scan_connection_.disconnect();
212  tf_listener_.reset(); // listener may access lase_scan_filter_, so it should be reset earlier
213  laser_scan_filter_.reset();
214  laser_scan_sub_.reset();
215 
216  // Map
217  map_sub_.reset(); // map_sub_ may access map_, so it should be reset earlier
218  if (map_ != NULL) {
219  map_free(map_);
220  map_ = nullptr;
221  }
222  first_map_received_ = false;
223  free_space_indices.resize(0);
224 
225  // Transforms
226  tf_broadcaster_.reset();
227  tf_buffer_.reset();
228 
229  // PubSub
230  pose_pub_.reset();
231  particle_cloud_pub_.reset();
232 
233  // Odometry
234  motion_model_.reset();
235 
236  // Particle Filter
237  pf_free(pf_);
238  pf_ = nullptr;
239 
240  // Laser Scan
241  lasers_.clear();
242  lasers_update_.clear();
243  frame_to_laser_.clear();
244  force_update_ = true;
245 
246  // Do not persist an uninitialized pose over configured parameters.
247  if (set_initial_pose_ && initial_pose_is_known_) {
248  set_parameter(
249  rclcpp::Parameter(
250  "initial_pose.x",
251  rclcpp::ParameterValue(last_published_pose_.pose.pose.position.x)));
252  set_parameter(
253  rclcpp::Parameter(
254  "initial_pose.y",
255  rclcpp::ParameterValue(last_published_pose_.pose.pose.position.y)));
256  set_parameter(
257  rclcpp::Parameter(
258  "initial_pose.z",
259  rclcpp::ParameterValue(last_published_pose_.pose.pose.position.z)));
260  set_parameter(
261  rclcpp::Parameter(
262  "initial_pose.yaw",
263  rclcpp::ParameterValue(tf2::getYaw(last_published_pose_.pose.pose.orientation))));
264  }
265 
266  return nav2::CallbackReturn::SUCCESS;
267 }
268 
269 nav2::CallbackReturn
270 AmclNode::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
271 {
272  RCLCPP_INFO(get_logger(), "Shutting down");
273  return nav2::CallbackReturn::SUCCESS;
274 }
275 
276 bool
277 AmclNode::checkElapsedTime(std::chrono::seconds check_interval, rclcpp::Time last_time)
278 {
279  rclcpp::Duration elapsed_time = now() - last_time;
280  if (elapsed_time.nanoseconds() * 1e-9 > check_interval.count()) {
281  return true;
282  }
283  return false;
284 }
285 
286 #if NEW_UNIFORM_SAMPLING
287 std::vector<AmclNode::Point2D> AmclNode::free_space_indices;
288 #endif
289 
290 bool
291 AmclNode::getOdomPose(
292  geometry_msgs::msg::PoseStamped & odom_pose,
293  double & x, double & y, double & yaw,
294  const rclcpp::Time & sensor_timestamp, const std::string & frame_id)
295 {
296  // Get the robot's pose
297  geometry_msgs::msg::PoseStamped ident;
298  ident.header.frame_id = frame_id;
299  ident.header.stamp = sensor_timestamp;
300  tf2::toMsg(tf2::Transform::getIdentity(), ident.pose);
301 
302  try {
303  tf_buffer_->transform(ident, odom_pose, odom_frame_id_);
304  } catch (tf2::TransformException & e) {
305  ++scan_error_count_;
306  if (scan_error_count_ % 20 == 0) {
307  RCLCPP_ERROR(
308  get_logger(), "(%d) consecutive laser scan transforms failed: (%s)", scan_error_count_,
309  e.what());
310  }
311  return false;
312  }
313 
314  scan_error_count_ = 0; // reset since we got a good transform
315  x = odom_pose.pose.position.x;
316  y = odom_pose.pose.position.y;
317  yaw = tf2::getYaw(odom_pose.pose.orientation);
318 
319  return true;
320 }
321 
323 AmclNode::uniformPoseGenerator(void * arg)
324 {
325  map_t * map = reinterpret_cast<map_t *>(arg);
326 
327 #if NEW_UNIFORM_SAMPLING
328  unsigned int rand_index = drand48() * free_space_indices.size();
329  AmclNode::Point2D free_point = free_space_indices[rand_index];
330  pf_vector_t p;
331  p.v[0] = MAP_WXGX(map, free_point.x);
332  p.v[1] = MAP_WYGY(map, free_point.y);
333  p.v[2] = drand48() * 2 * M_PI - M_PI;
334 #else
335  double min_x, max_x, min_y, max_y;
336 
337  min_x = (map->size_x * map->scale) / 2.0 - map->origin_x;
338  max_x = (map->size_x * map->scale) / 2.0 + map->origin_x;
339  min_y = (map->size_y * map->scale) / 2.0 - map->origin_y;
340  max_y = (map->size_y * map->scale) / 2.0 + map->origin_y;
341 
342  pf_vector_t p;
343 
344  RCLCPP_DEBUG(get_logger(), "Generating new uniform sample");
345  for (;; ) {
346  p.v[0] = min_x + drand48() * (max_x - min_x);
347  p.v[1] = min_y + drand48() * (max_y - min_y);
348  p.v[2] = drand48() * 2 * M_PI - M_PI;
349  // Check that it's a free cell
350  int i, j;
351  i = MAP_GXWX(map, p.v[0]);
352  j = MAP_GYWY(map, p.v[1]);
353  if (MAP_VALID(map, i, j) && (map->cells[MAP_INDEX(map, i, j)].occ_state == -1)) {
354  break;
355  }
356  }
357 #endif
358  return p;
359 }
360 
361 void
362 AmclNode::globalLocalizationCallback(
363  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
364  const std::shared_ptr<std_srvs::srv::Empty::Request>/*req*/,
365  std::shared_ptr<std_srvs::srv::Empty::Response>/*res*/)
366 {
367  std::lock_guard<std::recursive_mutex> cfl(mutex_);
368 
369  if (!map_) {
370  RCLCPP_ERROR(get_logger(), "Cannot initialize globally before a map is received");
371  return;
372  }
373 
374  RCLCPP_INFO(get_logger(), "Initializing with uniform distribution");
375 
376  pf_init_model(
377  pf_, (pf_init_model_fn_t)AmclNode::uniformPoseGenerator,
378  reinterpret_cast<void *>(map_));
379  RCLCPP_INFO(get_logger(), "Global initialisation done!");
380  initial_pose_is_known_ = true;
381  pf_init_ = false;
382 }
383 
384 void
385 AmclNode::initialPoseReceivedSrv(
386  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
387  const std::shared_ptr<nav2_msgs::srv::SetInitialPose::Request> req,
388  std::shared_ptr<nav2_msgs::srv::SetInitialPose::Response>/*res*/)
389 {
390  initialPoseReceived(std::make_shared<geometry_msgs::msg::PoseWithCovarianceStamped>(req->pose));
391 }
392 
393 // force nomotion updates (amcl updating without requiring motion)
394 void
395 AmclNode::nomotionUpdateCallback(
396  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
397  const std::shared_ptr<std_srvs::srv::Empty::Request>/*req*/,
398  std::shared_ptr<std_srvs::srv::Empty::Response>/*res*/)
399 {
400  RCLCPP_INFO(get_logger(), "Requesting no-motion update");
401  force_update_ = true;
402 }
403 
404 void
405 AmclNode::initialPoseReceived(
406  const geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr & msg)
407 {
408  std::lock_guard<std::recursive_mutex> cfl(mutex_);
409 
410  RCLCPP_INFO(get_logger(), "initialPoseReceived");
411 
412  if (!nav2::validateMsg(*msg)) {
413  RCLCPP_ERROR(get_logger(), "Received initialpose message is malformed. Rejecting.");
414  return;
415  }
416  if (msg->header.frame_id != global_frame_id_) {
417  RCLCPP_WARN(
418  get_logger(),
419  "Ignoring initial pose in frame \"%s\"; initial poses must be in the global frame, \"%s\"",
420  msg->header.frame_id.c_str(),
421  global_frame_id_.c_str());
422  return;
423  }
424  if (first_map_received_) {
425  const int map_x = MAP_GXWX(map_, msg->pose.pose.position.x);
426  const int map_y = MAP_GYWY(map_, msg->pose.pose.position.y);
427  if (!MAP_VALID(map_, map_x, map_y)) {
428  RCLCPP_ERROR(
429  get_logger(), "Received initialpose from message is out of the size of map. Rejecting.");
430  return;
431  }
432  }
433 
434  // Overriding last published pose to initial pose
435  last_published_pose_ = *msg;
436 
437  if (!active_) {
438  init_pose_received_on_inactive = true;
439  RCLCPP_WARN(
440  get_logger(), "Received initial pose request, "
441  "but AMCL is not yet in the active state");
442  return;
443  }
444  handleInitialPose(last_published_pose_);
445 }
446 
447 void
448 AmclNode::handleInitialPose(geometry_msgs::msg::PoseWithCovarianceStamped & msg)
449 {
450  std::lock_guard<std::recursive_mutex> cfl(mutex_);
451  // In case the client sent us a pose estimate in the past, integrate the
452  // intervening odometric change.
453  geometry_msgs::msg::TransformStamped tx_odom;
454  try {
455  rclcpp::Time rclcpp_time = now();
456  tf2::TimePoint tf2_time(std::chrono::nanoseconds(rclcpp_time.nanoseconds()));
457 
458  // Check if the transform is available
459  tx_odom = tf_buffer_->lookupTransform(
460  base_frame_id_, tf2_ros::fromMsg(msg.header.stamp),
461  base_frame_id_, tf2_time, odom_frame_id_);
462  } catch (tf2::TransformException & e) {
463  // If we've never sent a transform, then this is normal, because the
464  // global_frame_id_ frame doesn't exist. We only care about in-time
465  // transformation for on-the-move pose-setting, so ignoring this
466  // startup condition doesn't really cost us anything.
467  if (sent_first_transform_) {
468  RCLCPP_WARN(get_logger(), "Failed to transform initial pose in time (%s)", e.what());
469  }
470  tf2::impl::Converter<false, true>::convert(tf2::Transform::getIdentity(), tx_odom.transform);
471  }
472 
473  tf2::Transform tx_odom_tf2;
474  tf2::impl::Converter<true, false>::convert(tx_odom.transform, tx_odom_tf2);
475 
476  tf2::Transform pose_old;
477  tf2::impl::Converter<true, false>::convert(msg.pose.pose, pose_old);
478 
479  tf2::Transform pose_new = pose_old * tx_odom_tf2;
480 
481  // Transform into the global frame
482 
483  RCLCPP_INFO(
484  get_logger(), "Setting pose (%.6f): %.3f %.3f %.3f",
485  now().nanoseconds() * 1e-9,
486  pose_new.getOrigin().x(),
487  pose_new.getOrigin().y(),
488  tf2::getYaw(pose_new.getRotation()));
489 
490  // Re-initialize the filter
491  pf_vector_t pf_init_pose_mean = pf_vector_zero();
492  pf_init_pose_mean.v[0] = pose_new.getOrigin().x();
493  pf_init_pose_mean.v[1] = pose_new.getOrigin().y();
494  pf_init_pose_mean.v[2] = tf2::getYaw(pose_new.getRotation());
495 
496  pf_matrix_t pf_init_pose_cov = pf_matrix_zero();
497  // Copy in the covariance, converting from 6-D to 3-D
498  for (int i = 0; i < 2; i++) {
499  for (int j = 0; j < 2; j++) {
500  pf_init_pose_cov.m[i][j] = msg.pose.covariance[6 * i + j];
501  }
502  }
503 
504  pf_init_pose_cov.m[2][2] = msg.pose.covariance[6 * 5 + 5];
505 
506  pf_init(pf_, pf_init_pose_mean, pf_init_pose_cov);
507  pf_init_ = false;
508  init_pose_received_on_inactive = false;
509  initial_pose_is_known_ = true;
510 }
511 
512 void
513 AmclNode::laserReceived(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_scan)
514 {
515  std::lock_guard<std::recursive_mutex> cfl(mutex_);
516 
517  // Since the sensor data is continually being published by the simulator or robot,
518  // we don't want our callbacks to fire until we're in the active state
519  if (!active_) {return;}
520  if (!first_map_received_) {
521  if (checkElapsedTime(2s, last_time_printed_msg_)) {
522  RCLCPP_WARN(get_logger(), "Waiting for map....");
523  last_time_printed_msg_ = now();
524  }
525  return;
526  }
527 
528  std::string laser_scan_frame_id = laser_scan->header.frame_id;
529  last_laser_received_ts_ = now();
530  int laser_index = -1;
531  geometry_msgs::msg::PoseStamped laser_pose;
532 
533  // Do we have the base->base_laser Tx yet?
534  if (frame_to_laser_.find(laser_scan_frame_id) == frame_to_laser_.end()) {
535  if (!addNewScanner(laser_index, laser_scan, laser_scan_frame_id, laser_pose)) {
536  return; // could not find transform
537  }
538  } else {
539  // we have the laser pose, retrieve laser index
540  laser_index = frame_to_laser_[laser_scan->header.frame_id];
541  }
542 
543  // Where was the robot when this scan was taken?
544  pf_vector_t pose;
545  if (!getOdomPose(
546  latest_odom_pose_, pose.v[0], pose.v[1], pose.v[2],
547  laser_scan->header.stamp, base_frame_id_))
548  {
549  RCLCPP_ERROR(get_logger(), "Couldn't determine robot's pose associated with laser scan");
550  return;
551  }
552 
553  pf_vector_t delta = pf_vector_zero();
554  bool force_publication = false;
555  if (!pf_init_) {
556  // Pose at last filter update
557  pf_odom_pose_ = pose;
558  pf_init_ = true;
559 
560  for (unsigned int i = 0; i < lasers_update_.size(); i++) {
561  lasers_update_[i] = true;
562  }
563 
564  force_publication = true;
565  resample_count_ = 0;
566  } else {
567  // Set the laser update flags
568  if (shouldUpdateFilter(pose, delta)) {
569  for (unsigned int i = 0; i < lasers_update_.size(); i++) {
570  lasers_update_[i] = true;
571  }
572  }
573  if (lasers_update_[laser_index]) {
574  motion_model_->odometryUpdate(pf_, pose, delta);
575  }
576  force_update_ = false;
577  }
578 
579  bool resampled = false;
580 
581  // If the robot has moved, update the filter
582  if (lasers_update_[laser_index]) {
583  updateFilter(laser_index, laser_scan, pose);
584 
585  // Resample the particles
586  if (!(++resample_count_ % resample_interval_)) {
587  pf_update_resample(pf_, reinterpret_cast<void *>(map_));
588  resampled = true;
589  }
590 
591  pf_sample_set_t * set = pf_->sets + pf_->current_set;
592  RCLCPP_DEBUG(get_logger(), "Num samples: %d\n", set->sample_count);
593 
594  if (!force_update_) {
595  publishParticleCloud(set);
596  }
597  }
598  if (resampled || force_publication || !first_pose_sent_) {
599  amcl_hyp_t max_weight_hyps;
600  std::vector<amcl_hyp_t> hyps;
601  int max_weight_hyp = -1;
602  if (getMaxWeightHyp(hyps, max_weight_hyps, max_weight_hyp)) {
603  publishAmclPose(laser_scan, hyps, max_weight_hyp);
604  calculateMaptoOdomTransform(laser_scan, hyps, max_weight_hyp);
605 
606  if (tf_broadcast_ == true) {
607  // We want to send a transform that is good up until a
608  // tolerance time so that odom can be used
609  auto stamp = tf2_ros::fromMsg(laser_scan->header.stamp);
610  tf2::TimePoint transform_expiration = stamp + transform_tolerance_;
611  sendMapToOdomTransform(transform_expiration);
612  sent_first_transform_ = true;
613  }
614  } else {
615  RCLCPP_ERROR(get_logger(), "No pose!");
616  }
617  } else if (latest_tf_valid_) {
618  if (tf_broadcast_ == true) {
619  // Nothing changed, so we'll just republish the last transform, to keep
620  // everybody happy.
621  tf2::TimePoint transform_expiration = tf2_ros::fromMsg(laser_scan->header.stamp) +
622  transform_tolerance_;
623  sendMapToOdomTransform(transform_expiration);
624  }
625  }
626 }
627 
628 bool AmclNode::addNewScanner(
629  int & laser_index,
630  const sensor_msgs::msg::LaserScan::ConstSharedPtr & laser_scan,
631  const std::string & laser_scan_frame_id,
632  geometry_msgs::msg::PoseStamped & laser_pose)
633 {
634  lasers_.push_back(createLaserObject());
635  lasers_update_.push_back(true);
636  laser_index = frame_to_laser_.size();
637 
638  geometry_msgs::msg::PoseStamped ident;
639  ident.header.frame_id = laser_scan_frame_id;
640  ident.header.stamp = rclcpp::Time();
641  tf2::toMsg(tf2::Transform::getIdentity(), ident.pose);
642  try {
643  tf_buffer_->transform(ident, laser_pose, base_frame_id_, transform_tolerance_);
644  } catch (tf2::TransformException & e) {
645  RCLCPP_ERROR(
646  get_logger(), "Couldn't transform from %s to %s, "
647  "even though the message notifier is in use: (%s)",
648  laser_scan->header.frame_id.c_str(),
649  base_frame_id_.c_str(), e.what());
650  return false;
651  }
652 
653  pf_vector_t laser_pose_v;
654  laser_pose_v.v[0] = laser_pose.pose.position.x;
655  laser_pose_v.v[1] = laser_pose.pose.position.y;
656  // laser mounting angle gets computed later -> set to 0 here!
657  laser_pose_v.v[2] = 0;
658  lasers_[laser_index]->SetLaserPose(laser_pose_v);
659  frame_to_laser_[laser_scan->header.frame_id] = laser_index;
660  return true;
661 }
662 
663 bool AmclNode::shouldUpdateFilter(const pf_vector_t pose, pf_vector_t & delta)
664 {
665  delta.v[0] = pose.v[0] - pf_odom_pose_.v[0];
666  delta.v[1] = pose.v[1] - pf_odom_pose_.v[1];
667  delta.v[2] = angleutils::angle_diff(pose.v[2], pf_odom_pose_.v[2]);
668 
669  // See if we should update the filter
670  bool update = fabs(delta.v[0]) > d_thresh_ ||
671  fabs(delta.v[1]) > d_thresh_ ||
672  fabs(delta.v[2]) > a_thresh_;
673  update = update || force_update_;
674  return update;
675 }
676 
677 bool AmclNode::updateFilter(
678  const int & laser_index,
679  const sensor_msgs::msg::LaserScan::ConstSharedPtr & laser_scan,
680  const pf_vector_t & pose)
681 {
682  nav2_amcl::LaserData ldata;
683  ldata.laser = lasers_[laser_index].get();
684  ldata.range_count = laser_scan->ranges.size();
685  // To account for lasers that are mounted upside-down, we determine the
686  // min, max, and increment angles of the laser in the base frame.
687  //
688  // Construct min and max angles of laser, in the base_link frame.
689  // Here we set the roll pitch yaw of the lasers. We assume roll and pitch are zero.
690  geometry_msgs::msg::QuaternionStamped min_q, inc_q;
691  min_q.header.stamp = laser_scan->header.stamp;
692  min_q.header.frame_id = laser_scan->header.frame_id;
693  min_q.quaternion = orientationAroundZAxis(laser_scan->angle_min);
694 
695  inc_q.header = min_q.header;
696  inc_q.quaternion = orientationAroundZAxis(laser_scan->angle_min + laser_scan->angle_increment);
697  try {
698  tf_buffer_->transform(min_q, min_q, base_frame_id_);
699  tf_buffer_->transform(inc_q, inc_q, base_frame_id_);
700  } catch (tf2::TransformException & e) {
701  RCLCPP_WARN(
702  get_logger(), "Unable to transform min/max laser angles into base frame: %s",
703  e.what());
704  return false;
705  }
706  double angle_min = tf2::getYaw(min_q.quaternion);
707  double angle_increment = tf2::getYaw(inc_q.quaternion) - angle_min;
708 
709  // wrapping angle to [-pi .. pi]
710  angle_increment = fmod(angle_increment + 5 * M_PI, 2 * M_PI) - M_PI;
711 
712  RCLCPP_DEBUG(
713  get_logger(), "Laser %d angles in base frame: min: %.3f inc: %.3f", laser_index, angle_min,
714  angle_increment);
715 
716  // Check the validity of range_max, must > 0.0
717  if (laser_scan->range_max <= 0.0) {
718  RCLCPP_WARN(
719  get_logger(), "wrong range_max of laser_scan data: %f. The message could be malformed."
720  " Ignore this message and stop updating.",
721  laser_scan->range_max);
722  return false;
723  }
724 
725  // Apply range min/max thresholds, if the user supplied them
726  if (laser_max_range_ > 0.0) {
727  ldata.range_max = std::min(laser_scan->range_max, static_cast<float>(laser_max_range_));
728  } else {
729  ldata.range_max = laser_scan->range_max;
730  }
731  double range_min;
732  if (laser_min_range_ > 0.0) {
733  range_min = std::max(laser_scan->range_min, static_cast<float>(laser_min_range_));
734  } else {
735  range_min = laser_scan->range_min;
736  }
737 
738  // The LaserData destructor will free this memory
739  ldata.ranges = new double[ldata.range_count][2];
740  for (int i = 0; i < ldata.range_count; i++) {
741  // amcl doesn't (yet) have a concept of min range. So we'll map short
742  // readings to max range.
743  if (laser_scan->ranges[i] <= range_min) {
744  ldata.ranges[i][0] = ldata.range_max;
745  } else {
746  ldata.ranges[i][0] = laser_scan->ranges[i];
747  }
748  // Compute bearing
749  ldata.ranges[i][1] = angle_min +
750  (i * angle_increment);
751  }
752  lasers_[laser_index]->sensorUpdate(pf_, reinterpret_cast<nav2_amcl::LaserData *>(&ldata));
753  lasers_update_[laser_index] = false;
754  pf_odom_pose_ = pose;
755  return true;
756 }
757 
758 void
759 AmclNode::publishParticleCloud(const pf_sample_set_t * set)
760 {
761  // If initial pose is not known, AMCL does not know the current pose
762  if (!initial_pose_is_known_) {return;}
763  auto cloud_with_weights_msg = std::make_unique<nav2_msgs::msg::ParticleCloud>();
764  cloud_with_weights_msg->header.stamp = this->now();
765  cloud_with_weights_msg->header.frame_id = global_frame_id_;
766  cloud_with_weights_msg->particles.resize(set->sample_count);
767 
768  for (int i = 0; i < set->sample_count; i++) {
769  cloud_with_weights_msg->particles[i].pose.position.x = set->samples[i].pose.v[0];
770  cloud_with_weights_msg->particles[i].pose.position.y = set->samples[i].pose.v[1];
771  cloud_with_weights_msg->particles[i].pose.position.z = 0;
772  cloud_with_weights_msg->particles[i].pose.orientation = orientationAroundZAxis(
773  set->samples[i].pose.v[2]);
774  cloud_with_weights_msg->particles[i].weight = set->samples[i].weight;
775  }
776 
777  particle_cloud_pub_->publish(std::move(cloud_with_weights_msg));
778 }
779 
780 bool
781 AmclNode::getMaxWeightHyp(
782  std::vector<amcl_hyp_t> & hyps, amcl_hyp_t & max_weight_hyps,
783  int & max_weight_hyp)
784 {
785  // Read out the current hypotheses
786  double max_weight = 0.0;
787  hyps.resize(pf_->sets[pf_->current_set].cluster_count);
788  for (int hyp_count = 0;
789  hyp_count < pf_->sets[pf_->current_set].cluster_count; hyp_count++)
790  {
791  double weight;
792  pf_vector_t pose_mean;
793  pf_matrix_t pose_cov;
794  if (!pf_get_cluster_stats(pf_, hyp_count, &weight, &pose_mean, &pose_cov)) {
795  RCLCPP_ERROR(get_logger(), "Couldn't get stats on cluster %d", hyp_count);
796  return false;
797  }
798 
799  hyps[hyp_count].weight = weight;
800  hyps[hyp_count].pf_pose_mean = pose_mean;
801  hyps[hyp_count].pf_pose_cov = pose_cov;
802 
803  if (hyps[hyp_count].weight > max_weight) {
804  max_weight = hyps[hyp_count].weight;
805  max_weight_hyp = hyp_count;
806  }
807  }
808 
809  if (max_weight > 0.0) {
810  RCLCPP_DEBUG(
811  get_logger(), "Max weight pose: %.3f %.3f %.3f",
812  hyps[max_weight_hyp].pf_pose_mean.v[0],
813  hyps[max_weight_hyp].pf_pose_mean.v[1],
814  hyps[max_weight_hyp].pf_pose_mean.v[2]);
815 
816  max_weight_hyps = hyps[max_weight_hyp];
817  return true;
818  }
819  return false;
820 }
821 
822 void
823 AmclNode::publishAmclPose(
824  const sensor_msgs::msg::LaserScan::ConstSharedPtr & laser_scan,
825  const std::vector<amcl_hyp_t> & hyps, const int & max_weight_hyp)
826 {
827  // If initial pose is not known, AMCL does not know the current pose
828  if (!initial_pose_is_known_) {
829  if (checkElapsedTime(2s, last_time_printed_msg_)) {
830  RCLCPP_WARN(
831  get_logger(), "AMCL cannot publish a pose or update the transform. "
832  "Please set the initial pose...");
833  last_time_printed_msg_ = now();
834  }
835  return;
836  }
837 
838  auto p = std::make_unique<geometry_msgs::msg::PoseWithCovarianceStamped>();
839  // Fill in the header
840  p->header.frame_id = global_frame_id_;
841  p->header.stamp = laser_scan->header.stamp;
842  // Copy in the pose
843  p->pose.pose.position.x = hyps[max_weight_hyp].pf_pose_mean.v[0];
844  p->pose.pose.position.y = hyps[max_weight_hyp].pf_pose_mean.v[1];
845  p->pose.pose.orientation = orientationAroundZAxis(hyps[max_weight_hyp].pf_pose_mean.v[2]);
846  // Copy in the covariance, converting from 3-D to 6-D
847  pf_sample_set_t * set = pf_->sets + pf_->current_set;
848  for (int i = 0; i < 2; i++) {
849  for (int j = 0; j < 2; j++) {
850  // Report the overall filter covariance, rather than the
851  // covariance for the highest-weight cluster
852  // p->covariance[6*i+j] = hyps[max_weight_hyp].pf_pose_cov.m[i][j];
853  p->pose.covariance[6 * i + j] = set->cov.m[i][j];
854  }
855  }
856  p->pose.covariance[6 * 5 + 5] = set->cov.m[2][2];
857  float temp = 0.0;
858  for (auto covariance_value : p->pose.covariance) {
859  temp += covariance_value;
860  }
861  temp += p->pose.pose.position.x + p->pose.pose.position.y;
862  if (!std::isnan(temp)) {
863  RCLCPP_DEBUG(get_logger(), "Publishing pose");
864  last_published_pose_ = *p;
865  first_pose_sent_ = true;
866  pose_pub_->publish(std::move(p));
867  } else {
868  RCLCPP_WARN(
869  get_logger(), "AMCL covariance or pose is NaN, likely due to an invalid "
870  "configuration or faulty sensor measurements! Pose is not available!");
871  }
872 
873  RCLCPP_DEBUG(
874  get_logger(), "New pose: %6.3f %6.3f %6.3f",
875  hyps[max_weight_hyp].pf_pose_mean.v[0],
876  hyps[max_weight_hyp].pf_pose_mean.v[1],
877  hyps[max_weight_hyp].pf_pose_mean.v[2]);
878 }
879 
880 void
881 AmclNode::calculateMaptoOdomTransform(
882  const sensor_msgs::msg::LaserScan::ConstSharedPtr & laser_scan,
883  const std::vector<amcl_hyp_t> & hyps, const int & max_weight_hyp)
884 {
885  // subtracting base to odom from map to base and send map to odom instead
886  geometry_msgs::msg::PoseStamped odom_to_map;
887  try {
888  tf2::Quaternion q;
889  q.setRPY(0, 0, hyps[max_weight_hyp].pf_pose_mean.v[2]);
890  tf2::Transform tmp_tf(q, tf2::Vector3(
891  hyps[max_weight_hyp].pf_pose_mean.v[0],
892  hyps[max_weight_hyp].pf_pose_mean.v[1],
893  0.0));
894 
895  geometry_msgs::msg::PoseStamped tmp_tf_stamped;
896  tmp_tf_stamped.header.frame_id = base_frame_id_;
897  tmp_tf_stamped.header.stamp = laser_scan->header.stamp;
898  tf2::toMsg(tmp_tf.inverse(), tmp_tf_stamped.pose);
899 
900  tf_buffer_->transform(tmp_tf_stamped, odom_to_map, odom_frame_id_);
901  } catch (tf2::TransformException & e) {
902  RCLCPP_DEBUG(get_logger(), "Failed to subtract base to odom transform: (%s)", e.what());
903  return;
904  }
905 
906  tf2::impl::Converter<true, false>::convert(odom_to_map.pose, latest_tf_);
907  latest_tf_valid_ = true;
908 }
909 
910 void
911 AmclNode::sendMapToOdomTransform(const tf2::TimePoint & transform_expiration)
912 {
913  // AMCL will update transform only when it has knowledge about robot's initial position
914  if (!initial_pose_is_known_) {return;}
915  geometry_msgs::msg::TransformStamped tmp_tf_stamped;
916  tmp_tf_stamped.header.frame_id = global_frame_id_;
917  tmp_tf_stamped.header.stamp = tf2_ros::toMsg(transform_expiration);
918  tmp_tf_stamped.child_frame_id = odom_frame_id_;
919  tf2::impl::Converter<false, true>::convert(latest_tf_.inverse(), tmp_tf_stamped.transform);
920  tf_broadcaster_->sendTransform(tmp_tf_stamped);
921 }
922 
923 std::unique_ptr<nav2_amcl::Laser>
924 AmclNode::createLaserObject()
925 {
926  RCLCPP_INFO(get_logger(), "createLaserObject");
927 
928  if (sensor_model_type_ == "beam") {
929  return std::make_unique<nav2_amcl::BeamModel>(
930  z_hit_, z_short_, z_max_, z_rand_, sigma_hit_, lambda_short_,
931  0.0, max_beams_, map_);
932  }
933 
934  if (sensor_model_type_ == "likelihood_field_prob") {
935  return std::make_unique<nav2_amcl::LikelihoodFieldModelProb>(
936  z_hit_, z_rand_, sigma_hit_,
937  laser_likelihood_max_dist_, do_beamskip_, beam_skip_distance_, beam_skip_threshold_,
938  beam_skip_error_threshold_, max_beams_, map_);
939  }
940 
941  return std::make_unique<nav2_amcl::LikelihoodFieldModel>(
942  z_hit_, z_rand_, sigma_hit_,
943  laser_likelihood_max_dist_, max_beams_, map_);
944 }
945 
946 void
947 AmclNode::initParameters()
948 {
949  double tmp_tol;
950 
951  alpha1_ = this->declare_or_get_parameter("alpha1", 0.2);
952  alpha2_ = this->declare_or_get_parameter("alpha2", 0.2);
953  alpha3_ = this->declare_or_get_parameter("alpha3", 0.2);
954  alpha4_ = this->declare_or_get_parameter("alpha4", 0.2);
955  alpha5_ = this->declare_or_get_parameter("alpha5", 0.2);
956  base_frame_id_ = this->declare_or_get_parameter("base_frame_id", std::string{"base_footprint"});
957  beam_skip_distance_ = this->declare_or_get_parameter("beam_skip_distance", 0.5);
958  beam_skip_error_threshold_ = this->declare_or_get_parameter("beam_skip_error_threshold", 0.9);
959  beam_skip_threshold_ = this->declare_or_get_parameter("beam_skip_threshold", 0.3);
960  do_beamskip_ = this->declare_or_get_parameter("do_beamskip", false);
961  global_frame_id_ = this->declare_or_get_parameter("global_frame_id", std::string{"map"});
962  lambda_short_ = this->declare_or_get_parameter("lambda_short", 0.1);
963  laser_likelihood_max_dist_ = this->declare_or_get_parameter("laser_likelihood_max_dist", 2.0);
964  laser_max_range_ = this->declare_or_get_parameter("laser_max_range", 100.0);
965  laser_min_range_ = this->declare_or_get_parameter("laser_min_range", -1.0);
966  sensor_model_type_ = this->declare_or_get_parameter(
967  "laser_model_type", std::string{"likelihood_field"});
968  set_initial_pose_ = this->declare_or_get_parameter("set_initial_pose", false);
969  initial_pose_x_ = this->declare_or_get_parameter("initial_pose.x", 0.0);
970  initial_pose_y_ = this->declare_or_get_parameter("initial_pose.y", 0.0);
971  initial_pose_z_ = this->declare_or_get_parameter("initial_pose.z", 0.0);
972  initial_pose_yaw_ = this->declare_or_get_parameter("initial_pose.yaw", 0.0);
973  max_beams_ = this->declare_or_get_parameter("max_beams", 60);
974  max_particles_ = this->declare_or_get_parameter("max_particles", 2000);
975  min_particles_ = this->declare_or_get_parameter("min_particles", 500);
976  odom_frame_id_ = this->declare_or_get_parameter("odom_frame_id", std::string{"odom"});
977  pf_err_ = this->declare_or_get_parameter("pf_err", 0.05);
978  pf_z_ = this->declare_or_get_parameter("pf_z", 0.99);
979  alpha_fast_ = this->declare_or_get_parameter("recovery_alpha_fast", 0.0);
980  alpha_slow_ = this->declare_or_get_parameter("recovery_alpha_slow", 0.0);
981  resample_interval_ = this->declare_or_get_parameter("resample_interval", 1);
982  robot_model_type_ = this->declare_or_get_parameter(
983  "robot_model_type", std::string{"nav2_amcl::DifferentialMotionModel"});
984  save_pose_rate_ = this->declare_or_get_parameter("save_pose_rate", 0.5);
985  initialize_at_saved_pose_ = this->declare_or_get_parameter("initialize_at_saved_pose", false);
986  saved_pose_filepath_ = this->declare_or_get_parameter(
987  "saved_pose_filepath", std::string("/tmp/amcl_saved_pose"));
988  sigma_hit_ = this->declare_or_get_parameter("sigma_hit", 0.2);
989  tf_broadcast_ = this->declare_or_get_parameter("tf_broadcast", true);
990  tmp_tol = this->declare_or_get_parameter("transform_tolerance", 1.0);
991  a_thresh_ = this->declare_or_get_parameter("update_min_a", 0.2);
992  d_thresh_ = this->declare_or_get_parameter("update_min_d", 0.25);
993  z_hit_ = this->declare_or_get_parameter("z_hit", 0.5);
994  z_max_ = this->declare_or_get_parameter("z_max", 0.05);
995  z_rand_ = this->declare_or_get_parameter("z_rand", 0.5);
996  z_short_ = this->declare_or_get_parameter("z_short", 0.05);
997  first_map_only_ = this->declare_or_get_parameter("first_map_only", false);
998  always_reset_initial_pose_ = this->declare_or_get_parameter("always_reset_initial_pose", false);
999  scan_topic_ = this->declare_or_get_parameter("scan_topic", std::string{"scan"});
1000  map_topic_ = this->declare_or_get_parameter("map_topic", std::string{"map"});
1001  freespace_downsampling_ = this->declare_or_get_parameter("freespace_downsampling", false);
1002  allow_parameter_qos_overrides_ = this->declare_or_get_parameter(
1003  "allow_parameter_qos_overrides", true);
1004  random_seed_ = this->declare_or_get_parameter("random_seed", -1);
1005 
1006  transform_tolerance_ = tf2::durationFromSec(tmp_tol);
1007  last_time_printed_msg_ = now();
1008 
1009  // Semantic checks
1010  if (laser_likelihood_max_dist_ < 0) {
1011  RCLCPP_WARN(
1012  get_logger(), "You've set laser_likelihood_max_dist to be negative,"
1013  " this isn't allowed so it will be set to default value 2.0.");
1014  laser_likelihood_max_dist_ = 2.0;
1015  }
1016  if (max_particles_ < 0) {
1017  RCLCPP_WARN(
1018  get_logger(), "You've set max_particles to be negative,"
1019  " this isn't allowed so it will be set to default value 2000.");
1020  max_particles_ = 2000;
1021  }
1022 
1023  if (min_particles_ < 0) {
1024  RCLCPP_WARN(
1025  get_logger(), "You've set min_particles to be negative,"
1026  " this isn't allowed so it will be set to default value 500.");
1027  min_particles_ = 500;
1028  }
1029 
1030  if (min_particles_ > max_particles_) {
1031  RCLCPP_WARN(
1032  get_logger(), "You've set min_particles to be greater than max particles,"
1033  " this isn't allowed so max_particles will be set to min_particles.");
1034  max_particles_ = min_particles_;
1035  }
1036 
1037  if (resample_interval_ <= 0) {
1038  RCLCPP_WARN(
1039  get_logger(), "You've set resample_interval to be zero or negative,"
1040  " this isn't allowed so it will be set to default value to 1.");
1041  resample_interval_ = 1;
1042  }
1043 
1044  if (always_reset_initial_pose_) {
1045  initial_pose_is_known_ = false;
1046  }
1047 }
1048 
1049 rcl_interfaces::msg::SetParametersResult AmclNode::validateParameterUpdatesCallback(
1050  const std::vector<rclcpp::Parameter> & parameters)
1051 {
1052  rcl_interfaces::msg::SetParametersResult result;
1053  result.successful = true;
1054  for (const auto & parameter : parameters) {
1055  const auto & param_type = parameter.get_type();
1056  const auto & param_name = parameter.get_name();
1057  if (param_name.find('.') != std::string::npos) {
1058  continue;
1059  }
1060  if (param_type == ParameterType::PARAMETER_DOUBLE) {
1061  if (param_name == "save_pose_rate") {
1062  // All values are valid
1063  continue;
1064  } else if (parameter.as_double() < 0.0 && // NOLINT(readability/braces)
1065  (param_name != "laser_min_range" || param_name != "laser_max_range"))
1066  {
1067  RCLCPP_WARN(
1068  get_logger(), "The value of parameter '%s' is incorrectly set to %f, "
1069  "it should be >=0. Ignoring parameter update.",
1070  param_name.c_str(), parameter.as_double());
1071  result.successful = false;
1072  }
1073  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
1074  if (parameter.as_int() <= 0.0 && param_name == "resample_interval") {
1075  RCLCPP_WARN(
1076  get_logger(), "The value of resample_interval is incorrectly set, "
1077  "it should be >0. Ignoring parameter update.");
1078  result.successful = false;
1079  } else if (parameter.as_int() < 0.0) {
1080  RCLCPP_WARN(
1081  get_logger(), "The value of parameter '%s' is incorrectly set to %ld, "
1082  "it should be >=0. Ignoring parameter update.",
1083  param_name.c_str(), parameter.as_int());
1084  result.successful = false;
1085  } else if (param_name == "max_particles" && parameter.as_int() < min_particles_) {
1086  RCLCPP_WARN(
1087  get_logger(), "The value of max_particles is incorrectly set, "
1088  "it should be larger than min_particles. Ignoring parameter update.");
1089  result.successful = false;
1090  } else if (param_name == "min_particles" && parameter.as_int() > max_particles_) {
1091  RCLCPP_WARN(
1092  get_logger(), "The value of min_particles is incorrectly set, "
1093  "it should be smaller than max particles. Ignoring parameter update.");
1094  result.successful = false;
1095  }
1096  }
1097  }
1098  return result;
1099 }
1100 
1101 void
1102 AmclNode::updateParametersCallback(
1103  const std::vector<rclcpp::Parameter> & parameters)
1104 {
1105  std::lock_guard<std::recursive_mutex> cfl(mutex_);
1106 
1107  bool reinit_pf = false;
1108  bool reinit_odom = false;
1109  bool reinit_laser = false;
1110  bool reinit_map = false;
1111 
1112  for (const auto & parameter : parameters) {
1113  const auto & param_type = parameter.get_type();
1114  const auto & param_name = parameter.get_name();
1115  if (param_name.find('.') != std::string::npos) {
1116  continue;
1117  }
1118  if (param_type == ParameterType::PARAMETER_DOUBLE) {
1119  if (param_name == "alpha1") {
1120  alpha1_ = parameter.as_double();
1121  reinit_odom = true;
1122  } else if (param_name == "alpha2") {
1123  alpha2_ = parameter.as_double();
1124  reinit_odom = true;
1125  } else if (param_name == "alpha3") {
1126  alpha3_ = parameter.as_double();
1127  reinit_odom = true;
1128  } else if (param_name == "alpha4") {
1129  alpha4_ = parameter.as_double();
1130  reinit_odom = true;
1131  } else if (param_name == "alpha5") {
1132  alpha5_ = parameter.as_double();
1133  reinit_odom = true;
1134  } else if (param_name == "beam_skip_distance") {
1135  beam_skip_distance_ = parameter.as_double();
1136  reinit_laser = true;
1137  } else if (param_name == "beam_skip_error_threshold") {
1138  beam_skip_error_threshold_ = parameter.as_double();
1139  reinit_laser = true;
1140  } else if (param_name == "beam_skip_threshold") {
1141  beam_skip_threshold_ = parameter.as_double();
1142  reinit_laser = true;
1143  } else if (param_name == "lambda_short") {
1144  lambda_short_ = parameter.as_double();
1145  reinit_laser = true;
1146  } else if (param_name == "laser_likelihood_max_dist") {
1147  laser_likelihood_max_dist_ = parameter.as_double();
1148  reinit_laser = true;
1149  } else if (param_name == "laser_max_range") {
1150  laser_max_range_ = parameter.as_double();
1151  reinit_laser = true;
1152  } else if (param_name == "laser_min_range") {
1153  laser_min_range_ = parameter.as_double();
1154  reinit_laser = true;
1155  } else if (param_name == "pf_err") {
1156  pf_err_ = parameter.as_double();
1157  reinit_pf = true;
1158  } else if (param_name == "pf_z") {
1159  pf_z_ = parameter.as_double();
1160  reinit_pf = true;
1161  } else if (param_name == "recovery_alpha_fast") {
1162  alpha_fast_ = parameter.as_double();
1163  reinit_pf = true;
1164  } else if (param_name == "recovery_alpha_slow") {
1165  alpha_slow_ = parameter.as_double();
1166  reinit_pf = true;
1167  } else if (param_name == "save_pose_rate") {
1168  save_pose_rate_ = parameter.as_double();
1169  } else if (param_name == "sigma_hit") {
1170  sigma_hit_ = parameter.as_double();
1171  reinit_laser = true;
1172  } else if (param_name == "transform_tolerance") {
1173  double tmp_tol = parameter.as_double();
1174  transform_tolerance_ = tf2::durationFromSec(tmp_tol);
1175  reinit_laser = true;
1176  } else if (param_name == "update_min_a") {
1177  a_thresh_ = parameter.as_double();
1178  } else if (param_name == "update_min_d") {
1179  d_thresh_ = parameter.as_double();
1180  } else if (param_name == "z_hit") {
1181  z_hit_ = parameter.as_double();
1182  reinit_laser = true;
1183  } else if (param_name == "z_max") {
1184  z_max_ = parameter.as_double();
1185  reinit_laser = true;
1186  } else if (param_name == "z_rand") {
1187  z_rand_ = parameter.as_double();
1188  reinit_laser = true;
1189  } else if (param_name == "z_short") {
1190  z_short_ = parameter.as_double();
1191  reinit_laser = true;
1192  }
1193  } else if (param_type == ParameterType::PARAMETER_STRING) {
1194  if (param_name == "base_frame_id") {
1195  base_frame_id_ = parameter.as_string();
1196  } else if (param_name == "global_frame_id") {
1197  global_frame_id_ = parameter.as_string();
1198  } else if (param_name == "map_topic") {
1199  map_topic_ = parameter.as_string();
1200  reinit_map = true;
1201  } else if (param_name == "laser_model_type") {
1202  sensor_model_type_ = parameter.as_string();
1203  reinit_laser = true;
1204  } else if (param_name == "odom_frame_id") {
1205  odom_frame_id_ = parameter.as_string();
1206  reinit_laser = true;
1207  } else if (param_name == "scan_topic") {
1208  scan_topic_ = parameter.as_string();
1209  reinit_laser = true;
1210  } else if (param_name == "robot_model_type") {
1211  robot_model_type_ = parameter.as_string();
1212  reinit_odom = true;
1213  } else if (param_name == "saved_pose_filepath") {
1214  saved_pose_filepath_ = parameter.as_string();
1215  }
1216  } else if (param_type == ParameterType::PARAMETER_BOOL) {
1217  if (param_name == "do_beamskip") {
1218  do_beamskip_ = parameter.as_bool();
1219  reinit_laser = true;
1220  } else if (param_name == "tf_broadcast") {
1221  tf_broadcast_ = parameter.as_bool();
1222  } else if (param_name == "set_initial_pose") {
1223  set_initial_pose_ = parameter.as_bool();
1224  } else if (param_name == "first_map_only") {
1225  first_map_only_ = parameter.as_bool();
1226  } else if (param_name == "initialize_at_saved_pose") {
1227  initialize_at_saved_pose_ = parameter.as_bool();
1228  }
1229  } else if (param_type == ParameterType::PARAMETER_INTEGER) {
1230  if (param_name == "max_beams") {
1231  max_beams_ = parameter.as_int();
1232  reinit_laser = true;
1233  } else if (param_name == "max_particles") {
1234  max_particles_ = parameter.as_int();
1235  reinit_pf = true;
1236  } else if (param_name == "min_particles") {
1237  min_particles_ = parameter.as_int();
1238  reinit_pf = true;
1239  } else if (param_name == "resample_interval") {
1240  resample_interval_ = parameter.as_int();
1241  }
1242  }
1243  }
1244 
1245  // Re-initialize the particle filter
1246  if (reinit_pf) {
1247  if (pf_ != NULL) {
1248  pf_free(pf_);
1249  pf_ = NULL;
1250  }
1251  initParticleFilter();
1252  }
1253 
1254  // Re-initialize the odometry
1255  if (reinit_odom) {
1256  motion_model_.reset();
1257  initOdometry();
1258  }
1259 
1260  // Re-initialize the lasers and it's filters
1261  if (reinit_laser) {
1262  lasers_.clear();
1263  lasers_update_.clear();
1264  frame_to_laser_.clear();
1265  laser_scan_connection_.disconnect();
1266  laser_scan_filter_.reset();
1267  laser_scan_sub_.reset();
1268 
1269  initMessageFilters();
1270  }
1271 
1272  // Re-initialize the map
1273  if (reinit_map) {
1274  map_sub_.reset();
1275  // Treat a new map topic as a new first-map epoch.
1276  first_map_received_ = false;
1277  map_sub_ = create_subscription<nav_msgs::msg::OccupancyGrid>(
1278  map_topic_,
1279  std::bind(&AmclNode::mapReceived, this, std::placeholders::_1),
1281  }
1282 }
1283 
1284 void
1285 AmclNode::mapReceived(const nav_msgs::msg::OccupancyGrid::ConstSharedPtr & msg)
1286 {
1287  RCLCPP_DEBUG(get_logger(), "AmclNode: A new map was received.");
1288  if (!nav2::validateMsg(*msg)) {
1289  RCLCPP_ERROR(get_logger(), "Received map message is malformed. Rejecting.");
1290  return;
1291  }
1292  if (first_map_only_ && first_map_received_) {
1293  return;
1294  }
1295  handleMapMessage(*msg);
1296  first_map_received_ = true;
1297 }
1298 
1299 void
1300 AmclNode::handleMapMessage(const nav_msgs::msg::OccupancyGrid & msg)
1301 {
1302  std::lock_guard<std::recursive_mutex> cfl(mutex_);
1303 
1304  RCLCPP_INFO(
1305  get_logger(), "Received a %d X %d map @ %.3f m/pix",
1306  msg.info.width,
1307  msg.info.height,
1308  msg.info.resolution);
1309  if (msg.header.frame_id != global_frame_id_) {
1310  RCLCPP_WARN(
1311  get_logger(), "Frame_id of map received:'%s' doesn't match global_frame_id:'%s'. This could"
1312  " cause issues with reading published topics",
1313  msg.header.frame_id.c_str(),
1314  global_frame_id_.c_str());
1315  }
1316  freeMapDependentMemory();
1317  map_ = convertMap(msg);
1318 
1319 #if NEW_UNIFORM_SAMPLING
1320  createFreeSpaceVector();
1321 #endif
1322 }
1323 
1324 void
1325 AmclNode::createFreeSpaceVector()
1326 {
1327  int delta = freespace_downsampling_ ? 2 : 1;
1328  // Index of free space
1329  free_space_indices.resize(0);
1330  for (int i = 0; i < map_->size_x; i += delta) {
1331  for (int j = 0; j < map_->size_y; j += delta) {
1332  if (map_->cells[MAP_INDEX(map_, i, j)].occ_state == -1) {
1333  AmclNode::Point2D point = {i, j};
1334  free_space_indices.push_back(point);
1335  }
1336  }
1337  }
1338 }
1339 
1340 void
1341 AmclNode::freeMapDependentMemory()
1342 {
1343  if (map_ != NULL) {
1344  map_free(map_);
1345  map_ = NULL;
1346  }
1347 
1348  // Clear queued laser objects because they hold pointers to the existing
1349  // map, #5202.
1350  lasers_.clear();
1351  lasers_update_.clear();
1352  frame_to_laser_.clear();
1353 }
1354 
1355 // Convert an OccupancyGrid map message into the internal representation. This function
1356 // allocates a map_t and returns it.
1357 map_t *
1358 AmclNode::convertMap(const nav_msgs::msg::OccupancyGrid & map_msg)
1359 {
1360  map_t * map = map_alloc();
1361 
1362  map->size_x = map_msg.info.width;
1363  map->size_y = map_msg.info.height;
1364  map->scale = map_msg.info.resolution;
1365  map->origin_x = map_msg.info.origin.position.x + (map->size_x / 2) * map->scale;
1366  map->origin_y = map_msg.info.origin.position.y + (map->size_y / 2) * map->scale;
1367 
1368  map->cells =
1369  reinterpret_cast<map_cell_t *>(malloc(sizeof(map_cell_t) * map->size_x * map->size_y));
1370 
1371  // Convert to player format
1372  for (int i = 0; i < map->size_x * map->size_y; i++) {
1373  if (map_msg.data[i] == 0) {
1374  map->cells[i].occ_state = -1;
1375  } else if (map_msg.data[i] == 100) {
1376  map->cells[i].occ_state = +1;
1377  } else {
1378  map->cells[i].occ_state = 0;
1379  }
1380  }
1381 
1382  return map;
1383 }
1384 
1385 void
1386 AmclNode::initTransforms()
1387 {
1388  RCLCPP_INFO(get_logger(), "initTransforms");
1389 
1390  // Initialize transform listener and broadcaster
1391  tf_buffer_ = nav2::create_transform_buffer(this, callback_group_);
1392  tf_listener_ = nav2::create_transform_listener(*tf_buffer_, this, true);
1393  tf_broadcaster_ = nav2::create_transform_broadcaster(shared_from_this());
1394 
1395  sent_first_transform_ = false;
1396  latest_tf_valid_ = false;
1397  latest_tf_ = tf2::Transform::getIdentity();
1398 }
1399 
1400 void
1401 AmclNode::initMessageFilters()
1402 {
1403  auto sub_opt = nav2::interfaces::createSubscriptionOptions(
1404  scan_topic_, allow_parameter_qos_overrides_);
1405 
1406  #if RCLCPP_VERSION_GTE(29, 6, 0)
1407  laser_scan_sub_ = std::make_unique<message_filters::Subscriber<sensor_msgs::msg::LaserScan>>(
1408  shared_from_this(), scan_topic_, nav2::qos::SensorDataQoS(), sub_opt);
1409  #else
1410  laser_scan_sub_ = std::make_unique<message_filters::Subscriber<sensor_msgs::msg::LaserScan,
1411  rclcpp_lifecycle::LifecycleNode>>(
1412  std::static_pointer_cast<rclcpp_lifecycle::LifecycleNode>(shared_from_this()),
1413  scan_topic_, nav2::qos::SensorDataQoS().get_rmw_qos_profile(), sub_opt);
1414  #endif
1415 
1416  laser_scan_filter_ = nav2::create_message_filter<sensor_msgs::msg::LaserScan>(
1417  *laser_scan_sub_, *tf_buffer_, odom_frame_id_, 10,
1418  this, transform_tolerance_);
1419 
1420 
1421  laser_scan_connection_ = laser_scan_filter_->registerCallback(
1422  std::bind(&AmclNode::laserReceived, this, std::placeholders::_1));
1423 }
1424 
1425 void
1426 AmclNode::initPubSub()
1427 {
1428  RCLCPP_INFO(get_logger(), "initPubSub");
1429 
1430  particle_cloud_pub_ = create_publisher<nav2_msgs::msg::ParticleCloud>(
1431  "particle_cloud",
1433 
1434  pose_pub_ = create_publisher<geometry_msgs::msg::PoseWithCovarianceStamped>(
1435  "amcl_pose",
1437 
1438  initial_pose_sub_ = create_subscription<geometry_msgs::msg::PoseWithCovarianceStamped>(
1439  "initialpose",
1440  std::bind(&AmclNode::initialPoseReceived, this, std::placeholders::_1));
1441 
1442  map_sub_ = create_subscription<nav_msgs::msg::OccupancyGrid>(
1443  map_topic_,
1444  std::bind(&AmclNode::mapReceived, this, std::placeholders::_1),
1446 
1447  RCLCPP_INFO(get_logger(), "Subscribed to map topic.");
1448 }
1449 
1450 void
1451 AmclNode::initServices()
1452 {
1453  global_loc_srv_ = create_service<std_srvs::srv::Empty>(
1454  "reinitialize_global_localization",
1455  std::bind(
1456  &AmclNode::globalLocalizationCallback, this, std::placeholders::_1,
1457  std::placeholders::_2, std::placeholders::_3));
1458 
1459  initial_guess_srv_ = create_service<nav2_msgs::srv::SetInitialPose>(
1460  "set_initial_pose",
1461  std::bind(
1462  &AmclNode::initialPoseReceivedSrv, this, std::placeholders::_1, std::placeholders::_2,
1463  std::placeholders::_3));
1464 
1465  nomotion_update_srv_ = create_service<std_srvs::srv::Empty>(
1466  "request_nomotion_update",
1467  std::bind(
1468  &AmclNode::nomotionUpdateCallback, this, std::placeholders::_1, std::placeholders::_2,
1469  std::placeholders::_3));
1470 }
1471 
1472 void
1473 AmclNode::initOdometry()
1474 {
1475  // TODO(mjeronimo): We should handle persistence of the last known pose of the robot. We could
1476  // then read that pose here and initialize using that.
1477 
1478  // When pausing and resuming, remember the last robot pose so we don't start at 0:0 again
1479  init_pose_[0] = last_published_pose_.pose.pose.position.x;
1480  init_pose_[1] = last_published_pose_.pose.pose.position.y;
1481  init_pose_[2] = tf2::getYaw(last_published_pose_.pose.pose.orientation);
1482 
1483  if (!initial_pose_is_known_) {
1484  init_cov_[0] = 0.5 * 0.5;
1485  init_cov_[1] = 0.5 * 0.5;
1486  init_cov_[2] = (M_PI / 12.0) * (M_PI / 12.0);
1487  } else {
1488  init_cov_[0] = last_published_pose_.pose.covariance[0];
1489  init_cov_[1] = last_published_pose_.pose.covariance[7];
1490  init_cov_[2] = last_published_pose_.pose.covariance[35];
1491  }
1492 
1493  motion_model_ = plugin_loader_.createSharedInstance(robot_model_type_);
1494  motion_model_->initialize(alpha1_, alpha2_, alpha3_, alpha4_, alpha5_);
1495 
1496  latest_odom_pose_ = geometry_msgs::msg::PoseStamped();
1497 }
1498 
1499 void
1500 AmclNode::initParticleFilter()
1501 {
1502  // Create the particle filter
1503  pf_ = pf_alloc(
1504  min_particles_, max_particles_, alpha_slow_, alpha_fast_,
1505  (pf_init_model_fn_t)AmclNode::uniformPoseGenerator);
1506 
1507  // Seed RNG used by PF resampling and pose generation.
1508  // Keep legacy behavior (time-based) unless user explicitly sets a seed.
1509  if (random_seed_ >= 0) {
1510  // `srand48` expects a platform `long` seed. We avoid using `long` in our code and accept
1511  // truncation when seeding.
1512  srand48(static_cast<int>(random_seed_));
1513  } else {
1514  srand48(static_cast<int>(std::time(nullptr)));
1515  }
1516 
1517  pf_->pop_err = pf_err_;
1518  pf_->pop_z = pf_z_;
1519 
1520  // Initialize the filter
1521  pf_vector_t pf_init_pose_mean = pf_vector_zero();
1522  pf_init_pose_mean.v[0] = init_pose_[0];
1523  pf_init_pose_mean.v[1] = init_pose_[1];
1524  pf_init_pose_mean.v[2] = init_pose_[2];
1525 
1526  pf_matrix_t pf_init_pose_cov = pf_matrix_zero();
1527  pf_init_pose_cov.m[0][0] = init_cov_[0];
1528  pf_init_pose_cov.m[1][1] = init_cov_[1];
1529  pf_init_pose_cov.m[2][2] = init_cov_[2];
1530 
1531  pf_init(pf_, pf_init_pose_mean, pf_init_pose_cov);
1532 
1533  pf_init_ = false;
1534  resample_count_ = 0;
1535  memset(&pf_odom_pose_, 0, sizeof(pf_odom_pose_));
1536 }
1537 
1538 void
1539 AmclNode::initLaserScan()
1540 {
1541  scan_error_count_ = 0;
1542  last_laser_received_ts_ = rclcpp::Time(0);
1543 }
1544 
1545 void
1546 AmclNode::savePoseTimerCallback()
1547 {
1548  if (!active_ || !first_pose_sent_) {
1549  return;
1550  }
1551  savePoseToFile();
1552 }
1553 
1554 void
1555 AmclNode::savePoseToFile()
1556 {
1557  std::string tmp_path = saved_pose_filepath_ + ".tmp";
1558  try {
1559  std::ofstream file(tmp_path);
1560  if (!file.is_open()) {
1561  RCLCPP_WARN(
1562  get_logger(), "Failed to open pose file for writing: %s",
1563  tmp_path.c_str());
1564  return;
1565  }
1566 
1567  auto & pose = last_published_pose_;
1568  double timestamp = pose.header.stamp.sec +
1569  static_cast<double>(pose.header.stamp.nanosec) / 1e9;
1570  file << std::fixed << std::setprecision(9);
1571  file << "timestamp: " << timestamp << "\n";
1572  file << "frame_id: " << pose.header.frame_id << "\n";
1573  file << std::setprecision(6);
1574  file << "x: " << pose.pose.pose.position.x << "\n";
1575  file << "y: " << pose.pose.pose.position.y << "\n";
1576  file << "z: " << pose.pose.pose.position.z << "\n";
1577  file << "yaw: " << tf2::getYaw(pose.pose.pose.orientation) << "\n";
1578  file.close();
1579 
1580  // Atomic rename
1581  if (std::rename(tmp_path.c_str(), saved_pose_filepath_.c_str()) != 0) {
1582  RCLCPP_WARN(
1583  get_logger(), "Failed to rename pose file from %s to %s",
1584  tmp_path.c_str(), saved_pose_filepath_.c_str());
1585  }
1586  } catch (const std::exception & e) {
1587  RCLCPP_WARN(get_logger(), "Failed to save pose to file: %s", e.what());
1588  }
1589 }
1590 
1591 bool
1592 AmclNode::loadPoseFromFile(geometry_msgs::msg::PoseWithCovarianceStamped & pose)
1593 {
1594  std::ifstream file(saved_pose_filepath_);
1595  if (!file.is_open()) {
1596  return false;
1597  }
1598 
1599  try {
1600  std::string line;
1601  double x = 0.0, y = 0.0, z = 0.0, yaw = 0.0;
1602  double timestamp = 0.0;
1603  std::string frame_id;
1604 
1605  while (std::getline(file, line)) {
1606  if (line.empty() || line[0] == '#') {
1607  continue;
1608  }
1609  std::istringstream iss(line);
1610  std::string key;
1611  if (std::getline(iss, key, ':')) {
1612  if (key == "frame_id") {
1613  iss >> std::ws;
1614  std::getline(iss, frame_id);
1615  } else {
1616  double value;
1617  iss >> value;
1618  if (key == "x") {
1619  x = value;
1620  } else if (key == "y") {
1621  y = value;
1622  } else if (key == "z") {
1623  z = value;
1624  } else if (key == "yaw") {
1625  yaw = value;
1626  } else if (key == "timestamp") {
1627  timestamp = value;
1628  }
1629  }
1630  }
1631  }
1632 
1633  pose.header.frame_id = frame_id.empty() ? global_frame_id_ : frame_id;
1634  pose.header.stamp = now(); // Always use current time for relocalization
1635  pose.pose.pose.position.x = x;
1636  pose.pose.pose.position.y = y;
1637  pose.pose.pose.position.z = z;
1638  pose.pose.pose.orientation = orientationAroundZAxis(yaw);
1639 
1640  RCLCPP_INFO(
1641  get_logger(),
1642  "Loaded saved pose from file: x=%.3f, y=%.3f, z=%.3f, yaw=%.3f, "
1643  "originally saved at timestamp=%.3f, frame=%s",
1644  x, y, z, yaw, timestamp, pose.header.frame_id.c_str());
1645 
1646  return true;
1647  } catch (const std::exception & e) {
1648  RCLCPP_WARN(get_logger(), "Failed to parse saved pose file: %s", e.what());
1649  return false;
1650  }
1651 }
1652 
1653 } // namespace nav2_amcl
1654 
1655 #include "rclcpp_components/register_node_macro.hpp"
1656 
1657 // Register the component with class_loader.
1658 // This acts as a sort of entry point, allowing the component to be discoverable when its library
1659 // is being loaded into a running process.
1660 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_amcl::AmclNode)
A QoS profile for latched, reliable topics with a history of 1 messages.
A QoS profile for latched, reliable topics with a history of 10 messages.
A QoS profile for best-effort sensor data with a history of 10 messages.
Definition: map.hpp:62