Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
docking_server.cpp
1 // Copyright (c) 2024 Open Navigation LLC
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 #include "angles/angles.h"
16 #include "opennav_docking/docking_server.hpp"
17 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
18 #include "tf2/utils.h"
19 
20 using namespace std::chrono_literals;
21 using rcl_interfaces::msg::ParameterType;
22 using std::placeholders::_1;
23 
24 namespace opennav_docking
25 {
26 
27 DockingServer::DockingServer(const rclcpp::NodeOptions & options)
28 : nav2_util::LifecycleNode("docking_server", "", options)
29 {
30  RCLCPP_INFO(get_logger(), "Creating %s", get_name());
31 
32  declare_parameter("controller_frequency", 50.0);
33  declare_parameter("initial_perception_timeout", 5.0);
34  declare_parameter("wait_charge_timeout", 5.0);
35  declare_parameter("dock_approach_timeout", 30.0);
36  declare_parameter("undock_linear_tolerance", 0.05);
37  declare_parameter("undock_angular_tolerance", 0.05);
38  declare_parameter("max_retries", 3);
39  declare_parameter("base_frame", "base_link");
40  declare_parameter("fixed_frame", "odom");
41  declare_parameter("dock_backwards", false);
42  declare_parameter("dock_prestaging_tolerance", 0.5);
43 }
44 
45 nav2_util::CallbackReturn
46 DockingServer::on_configure(const rclcpp_lifecycle::State & state)
47 {
48  RCLCPP_INFO(get_logger(), "Configuring %s", get_name());
49  auto node = shared_from_this();
50 
51  get_parameter("controller_frequency", controller_frequency_);
52  get_parameter("initial_perception_timeout", initial_perception_timeout_);
53  get_parameter("wait_charge_timeout", wait_charge_timeout_);
54  get_parameter("dock_approach_timeout", dock_approach_timeout_);
55  get_parameter("undock_linear_tolerance", undock_linear_tolerance_);
56  get_parameter("undock_angular_tolerance", undock_angular_tolerance_);
57  get_parameter("max_retries", max_retries_);
58  get_parameter("base_frame", base_frame_);
59  get_parameter("fixed_frame", fixed_frame_);
60  get_parameter("dock_backwards", dock_backwards_);
61  get_parameter("dock_prestaging_tolerance", dock_prestaging_tolerance_);
62  RCLCPP_INFO(get_logger(), "Controller frequency set to %.4fHz", controller_frequency_);
63 
64  vel_publisher_ = std::make_unique<nav2_util::TwistPublisher>(node, "cmd_vel", 1);
65  tf2_buffer_ = std::make_shared<tf2_ros::Buffer>(node->get_clock());
66 
67  double action_server_result_timeout;
68  nav2_util::declare_parameter_if_not_declared(
69  node, "action_server_result_timeout", rclcpp::ParameterValue(10.0));
70  get_parameter("action_server_result_timeout", action_server_result_timeout);
71  rcl_action_server_options_t server_options = rcl_action_server_get_default_options();
72  server_options.result_timeout.nanoseconds = RCL_S_TO_NS(action_server_result_timeout);
73 
74  // Create the action servers for dock / undock
75  docking_action_server_ = std::make_unique<DockingActionServer>(
76  node, "dock_robot",
77  std::bind(&DockingServer::dockRobot, this),
78  nullptr, std::chrono::milliseconds(500),
79  true, server_options);
80 
81  undocking_action_server_ = std::make_unique<UndockingActionServer>(
82  node, "undock_robot",
83  std::bind(&DockingServer::undockRobot, this),
84  nullptr, std::chrono::milliseconds(500),
85  true, server_options);
86 
87  // Create composed utilities
88  mutex_ = std::make_shared<std::mutex>();
89  controller_ = std::make_unique<Controller>(node, tf2_buffer_, fixed_frame_, base_frame_);
90  navigator_ = std::make_unique<Navigator>(node);
91  dock_db_ = std::make_unique<DockDatabase>(mutex_);
92  if (!dock_db_->initialize(node, tf2_buffer_)) {
93  on_cleanup(state);
94  return nav2_util::CallbackReturn::FAILURE;
95  }
96 
97  return nav2_util::CallbackReturn::SUCCESS;
98 }
99 
100 nav2_util::CallbackReturn
101 DockingServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
102 {
103  RCLCPP_INFO(get_logger(), "Activating %s", get_name());
104 
105  auto node = shared_from_this();
106 
107  tf2_listener_ = std::make_unique<tf2_ros::TransformListener>(*tf2_buffer_, this, true);
108  dock_db_->activate();
109  navigator_->activate();
110  vel_publisher_->on_activate();
111  docking_action_server_->activate();
112  undocking_action_server_->activate();
113  curr_dock_type_.clear();
114 
115  // Add callback for dynamic parameters
116  dyn_params_handler_ = node->add_on_set_parameters_callback(
117  std::bind(&DockingServer::dynamicParametersCallback, this, _1));
118 
119  // Create bond connection
120  createBond();
121 
122  return nav2_util::CallbackReturn::SUCCESS;
123 }
124 
125 nav2_util::CallbackReturn
126 DockingServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
127 {
128  RCLCPP_INFO(get_logger(), "Deactivating %s", get_name());
129 
130  docking_action_server_->deactivate();
131  undocking_action_server_->deactivate();
132  dock_db_->deactivate();
133  navigator_->deactivate();
134  vel_publisher_->on_deactivate();
135 
136  remove_on_set_parameters_callback(dyn_params_handler_.get());
137  dyn_params_handler_.reset();
138  tf2_listener_.reset();
139 
140  // Destroy bond connection
141  destroyBond();
142 
143  return nav2_util::CallbackReturn::SUCCESS;
144 }
145 
146 nav2_util::CallbackReturn
147 DockingServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
148 {
149  RCLCPP_INFO(get_logger(), "Cleaning up %s", get_name());
150  tf2_buffer_.reset();
151  docking_action_server_.reset();
152  undocking_action_server_.reset();
153  dock_db_.reset();
154  navigator_.reset();
155  curr_dock_type_.clear();
156  controller_.reset();
157  vel_publisher_.reset();
158  return nav2_util::CallbackReturn::SUCCESS;
159 }
160 
161 nav2_util::CallbackReturn
162 DockingServer::on_shutdown(const rclcpp_lifecycle::State &)
163 {
164  RCLCPP_INFO(get_logger(), "Shutting down %s", get_name());
165  return nav2_util::CallbackReturn::SUCCESS;
166 }
167 
168 template<typename ActionT>
170  typename std::shared_ptr<const typename ActionT::Goal> goal,
171  const std::unique_ptr<nav2_util::SimpleActionServer<ActionT>> & action_server)
172 {
173  if (action_server->is_preempt_requested()) {
174  goal = action_server->accept_pending_goal();
175  }
176 }
177 
178 template<typename ActionT>
180  std::unique_ptr<nav2_util::SimpleActionServer<ActionT>> & action_server,
181  const std::string & name)
182 {
183  if (action_server->is_cancel_requested()) {
184  RCLCPP_WARN(get_logger(), "Goal was cancelled. Cancelling %s action", name.c_str());
185  return true;
186  }
187  return false;
188 }
189 
190 template<typename ActionT>
192  std::unique_ptr<nav2_util::SimpleActionServer<ActionT>> & action_server,
193  const std::string & name)
194 {
195  if (action_server->is_preempt_requested()) {
196  RCLCPP_WARN(get_logger(), "Goal was preempted. Cancelling %s action", name.c_str());
197  return true;
198  }
199  return false;
200 }
201 
203 {
204  std::lock_guard<std::mutex> lock(*mutex_);
205  action_start_time_ = this->now();
206  rclcpp::Rate loop_rate(controller_frequency_);
207 
208  auto goal = docking_action_server_->get_current_goal();
209  auto result = std::make_shared<DockRobot::Result>();
210  result->success = false;
211 
212  if (!docking_action_server_ || !docking_action_server_->is_server_active()) {
213  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
214  return;
215  }
216 
217  if (checkAndWarnIfCancelled(docking_action_server_, "dock_robot")) {
218  docking_action_server_->terminate_all();
219  return;
220  }
221 
222  getPreemptedGoalIfRequested(goal, docking_action_server_);
223  Dock * dock{nullptr};
224  num_retries_ = 0;
225 
226  try {
227  // Get dock (instance and plugin information) from request
228  if (goal->use_dock_id) {
229  RCLCPP_INFO(
230  get_logger(),
231  "Attempting to dock robot at %s.", goal->dock_id.c_str());
232  dock = dock_db_->findDock(goal->dock_id);
233  } else {
234  RCLCPP_INFO(
235  get_logger(),
236  "Attempting to dock robot at position (%0.2f, %0.2f).",
237  goal->dock_pose.pose.position.x, goal->dock_pose.pose.position.y);
238  dock = generateGoalDock(goal);
239  }
240 
241  // Check if robot is docked or charging before proceeding, only applicable to charging docks
242  if (dock->plugin->isCharger() && (dock->plugin->isDocked() || dock->plugin->isCharging())) {
243  RCLCPP_INFO(
244  get_logger(), "Robot is already docked and/or charging (if applicable), no need to dock");
245  result->success = true;
246  docking_action_server_->succeeded_current(result);
247  return;
248  }
249 
250  // Send robot to its staging pose
251  publishDockingFeedback(DockRobot::Feedback::NAV_TO_STAGING_POSE);
252  const auto initial_staging_pose = dock->getStagingPose();
253  const auto robot_pose = getRobotPoseInFrame(initial_staging_pose.header.frame_id);
254  if (!goal->navigate_to_staging_pose ||
255  utils::l2Norm(robot_pose.pose, initial_staging_pose.pose) < dock_prestaging_tolerance_)
256  {
257  RCLCPP_INFO(get_logger(), "Robot already within pre-staging pose tolerance for dock");
258  } else {
259  std::function<bool()> isPreempted = [this]() {
260  return checkAndWarnIfCancelled(docking_action_server_, "dock_robot") ||
261  checkAndWarnIfPreempted(docking_action_server_, "dock_robot");
262  };
263 
264  navigator_->goToPose(
265  initial_staging_pose,
266  rclcpp::Duration::from_seconds(goal->max_staging_time),
267  isPreempted);
268  RCLCPP_INFO(get_logger(), "Successful navigation to staging pose");
269  }
270 
271  // Construct initial estimate of where the dock is located in fixed_frame
272  auto dock_pose = utils::getDockPoseStamped(dock, rclcpp::Time(0));
273  tf2_buffer_->transform(dock_pose, dock_pose, fixed_frame_);
274 
275  // Get initial detection of dock before proceeding to move
276  doInitialPerception(dock, dock_pose);
277  RCLCPP_INFO(get_logger(), "Successful initial dock detection");
278 
279  // Docking control loop: while not docked, run controller
280  rclcpp::Time dock_contact_time;
281  while (rclcpp::ok()) {
282  try {
283  // Approach the dock using control law
284  if (approachDock(dock, dock_pose)) {
285  // We are docked, wait for charging to begin
286  RCLCPP_INFO(
287  get_logger(), "Made contact with dock, waiting for charge to start (if applicable).");
288  if (waitForCharge(dock)) {
289  if (dock->plugin->isCharger()) {
290  RCLCPP_INFO(get_logger(), "Robot is charging!");
291  } else {
292  RCLCPP_INFO(get_logger(), "Docking was successful!");
293  }
294  result->success = true;
295  result->num_retries = num_retries_;
296  stashDockData(goal->use_dock_id, dock, true);
298  docking_action_server_->succeeded_current(result);
299  return;
300  }
301  }
302 
303  // Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
304  stashDockData(goal->use_dock_id, dock, false);
306  docking_action_server_->terminate_all(result);
307  return;
309  if (++num_retries_ > max_retries_) {
310  RCLCPP_ERROR(get_logger(), "Failed to dock, all retries have been used");
311  throw;
312  }
313  RCLCPP_WARN(get_logger(), "Docking failed, will retry: %s", e.what());
314  }
315 
316  // Reset to staging pose to try again
317  if (!resetApproach(dock->getStagingPose())) {
318  // Cancelled, preempted, or shutting down
319  stashDockData(goal->use_dock_id, dock, false);
321  docking_action_server_->terminate_all(result);
322  return;
323  }
324  RCLCPP_INFO(get_logger(), "Returned to staging pose, attempting docking again");
325  }
326  } catch (const tf2::TransformException & e) {
327  RCLCPP_ERROR(get_logger(), "Transform error: %s", e.what());
328  result->error_code = DockRobot::Result::UNKNOWN;
329  } catch (opennav_docking_core::DockNotInDB & e) {
330  RCLCPP_ERROR(get_logger(), "%s", e.what());
331  result->error_code = DockRobot::Result::DOCK_NOT_IN_DB;
332  } catch (opennav_docking_core::DockNotValid & e) {
333  RCLCPP_ERROR(get_logger(), "%s", e.what());
334  result->error_code = DockRobot::Result::DOCK_NOT_VALID;
336  RCLCPP_ERROR(get_logger(), "%s", e.what());
337  result->error_code = DockRobot::Result::FAILED_TO_STAGE;
339  RCLCPP_ERROR(get_logger(), "%s", e.what());
340  result->error_code = DockRobot::Result::FAILED_TO_DETECT_DOCK;
342  RCLCPP_ERROR(get_logger(), "%s", e.what());
343  result->error_code = DockRobot::Result::FAILED_TO_CONTROL;
345  RCLCPP_ERROR(get_logger(), "%s", e.what());
346  result->error_code = DockRobot::Result::FAILED_TO_CHARGE;
348  RCLCPP_ERROR(get_logger(), "%s", e.what());
349  result->error_code = DockRobot::Result::UNKNOWN;
350  } catch (std::exception & e) {
351  RCLCPP_ERROR(get_logger(), "%s", e.what());
352  result->error_code = DockRobot::Result::UNKNOWN;
353  }
354 
355  // Store dock state for later undocking and delete temp dock, if applicable
356  stashDockData(goal->use_dock_id, dock, false);
357  result->num_retries = num_retries_;
359  docking_action_server_->terminate_current(result);
360 }
361 
362 void DockingServer::stashDockData(bool use_dock_id, Dock * dock, bool successful)
363 {
364  if (dock && successful) {
365  curr_dock_type_ = dock->type;
366  }
367 
368  if (!use_dock_id && dock) {
369  delete dock;
370  dock = nullptr;
371  }
372 }
373 
374 Dock * DockingServer::generateGoalDock(std::shared_ptr<const DockRobot::Goal> goal)
375 {
376  auto plugin = dock_db_->findDockPlugin(goal->dock_type);
377  if (!plugin) {
379  "Dock type '" + goal->dock_type + "' has no valid plugin!");
380  }
381 
382  auto dock = new Dock();
383  dock->frame = goal->dock_pose.header.frame_id;
384  dock->pose = goal->dock_pose.pose;
385  dock->type = goal->dock_type;
386  dock->plugin = plugin;
387  return dock;
388 }
389 
390 void DockingServer::doInitialPerception(Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose)
391 {
392  publishDockingFeedback(DockRobot::Feedback::INITIAL_PERCEPTION);
393  rclcpp::Rate loop_rate(controller_frequency_);
394  auto start = this->now();
395  auto timeout = rclcpp::Duration::from_seconds(initial_perception_timeout_);
396  while (!dock->plugin->getRefinedPose(dock_pose, dock->id)) {
397  if (this->now() - start > timeout) {
398  throw opennav_docking_core::FailedToDetectDock("Failed initial dock detection");
399  }
400 
401  if (checkAndWarnIfCancelled(docking_action_server_, "dock_robot") ||
402  checkAndWarnIfPreempted(docking_action_server_, "dock_robot"))
403  {
404  return;
405  }
406 
407  loop_rate.sleep();
408  }
409 }
410 
411 bool DockingServer::approachDock(Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose)
412 {
413  rclcpp::Rate loop_rate(controller_frequency_);
414  auto start = this->now();
415  auto timeout = rclcpp::Duration::from_seconds(dock_approach_timeout_);
416  while (rclcpp::ok()) {
417  publishDockingFeedback(DockRobot::Feedback::CONTROLLING);
418 
419  // Stop and report success if connected to dock
420  if (dock->plugin->isDocked() || (dock->plugin->isCharger() && dock->plugin->isCharging())) {
421  return true;
422  }
423 
424  // Stop if cancelled/preempted
425  if (checkAndWarnIfCancelled(docking_action_server_, "dock_robot") ||
426  checkAndWarnIfPreempted(docking_action_server_, "dock_robot"))
427  {
428  return false;
429  }
430 
431  // Update perception
432  if (!dock->plugin->getRefinedPose(dock_pose, dock->id)) {
433  throw opennav_docking_core::FailedToDetectDock("Failed dock detection");
434  }
435 
436  // Transform target_pose into base_link frame
437  geometry_msgs::msg::PoseStamped target_pose = dock_pose;
438  target_pose.header.stamp = rclcpp::Time(0);
439 
440  // The control law can get jittery when close to the end when atan2's can explode.
441  // Thus, we backward project the controller's target pose a little bit after the
442  // dock so that the robot never gets to the end of the spiral before its in contact
443  // with the dock to stop the docking procedure.
444  const double backward_projection = 0.25;
445  const double yaw = tf2::getYaw(target_pose.pose.orientation);
446  target_pose.pose.position.x += cos(yaw) * backward_projection;
447  target_pose.pose.position.y += sin(yaw) * backward_projection;
448  tf2_buffer_->transform(target_pose, target_pose, base_frame_);
449 
450  // Make sure that the target pose is pointing at the robot when moving backwards
451  // This is to ensure that the robot doesn't try to dock from the wrong side
452  if (dock_backwards_) {
453  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
454  tf2::getYaw(target_pose.pose.orientation) + M_PI);
455  }
456 
457  // Compute and publish controls
458  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
459  command->header.stamp = now();
460  if (!controller_->computeVelocityCommand(target_pose.pose, command->twist, true,
461  dock_backwards_))
462  {
463  throw opennav_docking_core::FailedToControl("Failed to get control");
464  }
465  vel_publisher_->publish(std::move(command));
466 
467  if (this->now() - start > timeout) {
469  "Timed out approaching dock; dock nor charging (if applicable) detected");
470  }
471 
472  loop_rate.sleep();
473  }
474  return false;
475 }
476 
478 {
479  // This is a non-charger docking request
480  if (!dock->plugin->isCharger()) {
481  return true;
482  }
483 
484  rclcpp::Rate loop_rate(controller_frequency_);
485  auto start = this->now();
486  auto timeout = rclcpp::Duration::from_seconds(wait_charge_timeout_);
487  while (rclcpp::ok()) {
488  publishDockingFeedback(DockRobot::Feedback::WAIT_FOR_CHARGE);
489 
490  if (dock->plugin->isCharging()) {
491  return true;
492  }
493 
494  if (checkAndWarnIfCancelled(docking_action_server_, "dock_robot") ||
495  checkAndWarnIfPreempted(docking_action_server_, "dock_robot"))
496  {
497  return false;
498  }
499 
500  if (this->now() - start > timeout) {
501  throw opennav_docking_core::FailedToCharge("Timed out waiting for charge to start");
502  }
503 
504  loop_rate.sleep();
505  }
506  return false;
507 }
508 
509 bool DockingServer::resetApproach(const geometry_msgs::msg::PoseStamped & staging_pose)
510 {
511  rclcpp::Rate loop_rate(controller_frequency_);
512  auto start = this->now();
513  auto timeout = rclcpp::Duration::from_seconds(dock_approach_timeout_);
514  while (rclcpp::ok()) {
515  publishDockingFeedback(DockRobot::Feedback::INITIAL_PERCEPTION);
516 
517  // Stop if cancelled/preempted
518  if (checkAndWarnIfCancelled(docking_action_server_, "dock_robot") ||
519  checkAndWarnIfPreempted(docking_action_server_, "dock_robot"))
520  {
521  return false;
522  }
523 
524  // Compute and publish command
525  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
526  command->header.stamp = now();
527  if (getCommandToPose(
528  command->twist, staging_pose, undock_linear_tolerance_, undock_angular_tolerance_, false,
529  !dock_backwards_))
530  {
531  return true;
532  }
533  vel_publisher_->publish(std::move(command));
534 
535  if (this->now() - start > timeout) {
536  throw opennav_docking_core::FailedToControl("Timed out resetting dock approach");
537  }
538 
539  loop_rate.sleep();
540  }
541  return false;
542 }
543 
545  geometry_msgs::msg::Twist & cmd, const geometry_msgs::msg::PoseStamped & pose,
546  double linear_tolerance, double angular_tolerance, bool is_docking, bool backward)
547 {
548  // Reset command to zero velocity
549  cmd.linear.x = 0;
550  cmd.angular.z = 0;
551 
552  // Determine if we have reached pose yet & stop
553  geometry_msgs::msg::PoseStamped robot_pose = getRobotPoseInFrame(pose.header.frame_id);
554  const double dist = std::hypot(
555  robot_pose.pose.position.x - pose.pose.position.x,
556  robot_pose.pose.position.y - pose.pose.position.y);
557  const double yaw = angles::shortest_angular_distance(
558  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(pose.pose.orientation));
559  if (dist < linear_tolerance && abs(yaw) < angular_tolerance) {
560  return true;
561  }
562 
563  // Transform target_pose into base_link frame
564  geometry_msgs::msg::PoseStamped target_pose = pose;
565  target_pose.header.stamp = rclcpp::Time(0);
566  tf2_buffer_->transform(target_pose, target_pose, base_frame_);
567 
568  // Compute velocity command
569  if (!controller_->computeVelocityCommand(target_pose.pose, cmd, is_docking, backward)) {
570  throw opennav_docking_core::FailedToControl("Failed to get control");
571  }
572 
573  // Command is valid, but target is not reached
574  return false;
575 }
576 
578 {
579  std::lock_guard<std::mutex> lock(*mutex_);
580  action_start_time_ = this->now();
581  rclcpp::Rate loop_rate(controller_frequency_);
582 
583  auto goal = undocking_action_server_->get_current_goal();
584  auto result = std::make_shared<UndockRobot::Result>();
585  result->success = false;
586 
587  if (!undocking_action_server_ || !undocking_action_server_->is_server_active()) {
588  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
589  return;
590  }
591 
592  if (checkAndWarnIfCancelled(undocking_action_server_, "undock_robot")) {
593  undocking_action_server_->terminate_all(result);
594  return;
595  }
596 
597  getPreemptedGoalIfRequested(goal, undocking_action_server_);
598  auto max_duration = rclcpp::Duration::from_seconds(goal->max_undocking_time);
599 
600  try {
601  // Get dock plugin information from request or docked state, reset state.
602  std::string dock_type = curr_dock_type_;
603  if (!goal->dock_type.empty()) {
604  dock_type = goal->dock_type;
605  }
606 
607  ChargingDock::Ptr dock = dock_db_->findDockPlugin(dock_type);
608  if (!dock) {
609  throw opennav_docking_core::DockNotValid("No dock information to undock from!");
610  }
611  RCLCPP_INFO(
612  get_logger(),
613  "Attempting to undock robot of dock type %s.", dock->getName().c_str());
614 
615  // Check if the robot is docked before proceeding
616  if (dock->isCharger() && (!dock->isDocked() && !dock->isCharging())) {
617  RCLCPP_INFO(get_logger(), "Robot is not in the dock, no need to undock");
618  return;
619  }
620 
621  // Get "dock pose" by finding the robot pose
622  geometry_msgs::msg::PoseStamped dock_pose = getRobotPoseInFrame(fixed_frame_);
623 
624  // Make sure that the staging pose is pointing in the same direction when moving backwards
625  if (dock_backwards_) {
626  dock_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
627  tf2::getYaw(dock_pose.pose.orientation) + M_PI);
628  }
629 
630  // Get staging pose (in fixed frame)
631  geometry_msgs::msg::PoseStamped staging_pose =
632  dock->getStagingPose(dock_pose.pose, dock_pose.header.frame_id);
633 
634  // Control robot to staging pose
635  rclcpp::Time loop_start = this->now();
636  while (rclcpp::ok()) {
637  // Stop if we exceed max duration
638  auto timeout = rclcpp::Duration::from_seconds(goal->max_undocking_time);
639  if (this->now() - loop_start > timeout) {
640  throw opennav_docking_core::FailedToControl("Undocking timed out");
641  }
642 
643  // Stop if cancelled/preempted
644  if (checkAndWarnIfCancelled(undocking_action_server_, "undock_robot") ||
645  checkAndWarnIfPreempted(undocking_action_server_, "undock_robot"))
646  {
648  undocking_action_server_->terminate_all(result);
649  return;
650  }
651 
652  // Don't control the robot until charging is disabled
653  if (dock->isCharger() && !dock->disableCharging()) {
654  loop_rate.sleep();
655  continue;
656  }
657 
658  // Get command to approach staging pose
659  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
660  command->header.stamp = now();
661  if (getCommandToPose(
662  command->twist, staging_pose, undock_linear_tolerance_, undock_angular_tolerance_, false,
663  !dock_backwards_))
664  {
665  RCLCPP_INFO(get_logger(), "Robot has reached staging pose");
666  // Have reached staging_pose
667  vel_publisher_->publish(std::move(command));
668  if (!dock->isCharger() || dock->hasStoppedCharging()) {
669  RCLCPP_INFO(get_logger(), "Robot has undocked!");
670  result->success = true;
671  curr_dock_type_.clear();
673  undocking_action_server_->succeeded_current(result);
674  return;
675  }
676  // Haven't stopped charging?
677  throw opennav_docking_core::FailedToControl("Failed to control off dock");
678  }
679 
680  // Publish command and sleep
681  vel_publisher_->publish(std::move(command));
682  loop_rate.sleep();
683  }
684  } catch (const tf2::TransformException & e) {
685  RCLCPP_ERROR(get_logger(), "Transform error: %s", e.what());
686  result->error_code = DockRobot::Result::UNKNOWN;
687  } catch (opennav_docking_core::DockNotValid & e) {
688  RCLCPP_ERROR(get_logger(), "%s", e.what());
689  result->error_code = DockRobot::Result::DOCK_NOT_VALID;
691  RCLCPP_ERROR(get_logger(), "%s", e.what());
692  result->error_code = DockRobot::Result::FAILED_TO_CONTROL;
694  RCLCPP_ERROR(get_logger(), "%s", e.what());
695  result->error_code = DockRobot::Result::UNKNOWN;
696  } catch (std::exception & e) {
697  RCLCPP_ERROR(get_logger(), "Internal error: %s", e.what());
698  result->error_code = DockRobot::Result::UNKNOWN;
699  }
700 
702  undocking_action_server_->terminate_current(result);
703 }
704 
705 geometry_msgs::msg::PoseStamped DockingServer::getRobotPoseInFrame(const std::string & frame)
706 {
707  geometry_msgs::msg::PoseStamped robot_pose;
708  robot_pose.header.frame_id = base_frame_;
709  robot_pose.header.stamp = rclcpp::Time(0);
710  tf2_buffer_->transform(robot_pose, robot_pose, frame);
711  return robot_pose;
712 }
713 
715 {
716  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
717  cmd_vel->header.stamp = now();
718  vel_publisher_->publish(std::move(cmd_vel));
719 }
720 
722 {
723  auto feedback = std::make_shared<DockRobot::Feedback>();
724  feedback->state = state;
725  feedback->docking_time = this->now() - action_start_time_;
726  feedback->num_retries = num_retries_;
727  docking_action_server_->publish_feedback(feedback);
728 }
729 
730 rcl_interfaces::msg::SetParametersResult
731 DockingServer::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
732 {
733  std::lock_guard<std::mutex> lock(*mutex_);
734 
735  rcl_interfaces::msg::SetParametersResult result;
736  for (auto parameter : parameters) {
737  const auto & type = parameter.get_type();
738  const auto & name = parameter.get_name();
739 
740  if (type == ParameterType::PARAMETER_DOUBLE) {
741  if (name == "controller_frequency") {
742  controller_frequency_ = parameter.as_double();
743  } else if (name == "initial_perception_timeout") {
744  initial_perception_timeout_ = parameter.as_double();
745  } else if (name == "wait_charge_timeout") {
746  wait_charge_timeout_ = parameter.as_double();
747  } else if (name == "undock_linear_tolerance") {
748  undock_linear_tolerance_ = parameter.as_double();
749  } else if (name == "undock_angular_tolerance") {
750  undock_angular_tolerance_ = parameter.as_double();
751  }
752  } else if (type == ParameterType::PARAMETER_STRING) {
753  if (name == "base_frame") {
754  base_frame_ = parameter.as_string();
755  } else if (name == "fixed_frame") {
756  fixed_frame_ = parameter.as_string();
757  }
758  } else if (type == ParameterType::PARAMETER_INTEGER) {
759  if (name == "max_retries") {
760  max_retries_ = parameter.as_int();
761  }
762  }
763  }
764 
765  result.successful = true;
766  return result;
767 }
768 
769 } // namespace opennav_docking
770 
771 #include "rclcpp_components/register_node_macro.hpp"
772 
773 // Register the component with class_loader.
774 // This acts as a sort of entry point, allowing the component to be discoverable when its library
775 // is being loaded into a running process.
776 RCLCPP_COMPONENTS_REGISTER_NODE(opennav_docking::DockingServer)
std::shared_ptr< nav2_util::LifecycleNode > shared_from_this()
Get a shared pointer of this.
void createBond()
Create bond connection to lifecycle manager.
void destroyBond()
Destroy bond connection to lifecycle manager.
An action server wrapper to make applications simpler using Actions.
An action server which implements charger docking node for AMRs.
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(std::vector< rclcpp::Parameter > parameters)
Callback executed when a parameter change is detected.
bool checkAndWarnIfPreempted(std::unique_ptr< nav2_util::SimpleActionServer< ActionT >> &action_server, const std::string &name)
Checks and logs warning if action preempted.
virtual geometry_msgs::msg::PoseStamped getRobotPoseInFrame(const std::string &frame)
Get the robot pose (aka base_frame pose) in another frame.
void dockRobot()
Main action callback method to complete docking request.
bool resetApproach(const geometry_msgs::msg::PoseStamped &staging_pose)
Reset the robot for another approach by controlling back to staging pose.
void doInitialPerception(Dock *dock, geometry_msgs::msg::PoseStamped &dock_pose)
Do initial perception, up to a timeout.
void publishDockingFeedback(uint16_t state)
Publish feedback from a docking action.
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivate member variables.
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Reset member variables.
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in shutdown state.
bool getCommandToPose(geometry_msgs::msg::Twist &cmd, const geometry_msgs::msg::PoseStamped &pose, double linear_tolerance, double angular_tolerance, bool is_docking, bool backward)
Run a single iteration of the control loop to approach a pose.
bool waitForCharge(Dock *dock)
Wait for charging to begin.
Dock * generateGoalDock(std::shared_ptr< const DockRobot::Goal > goal)
Generate a dock from action goal.
bool approachDock(Dock *dock, geometry_msgs::msg::PoseStamped &dock_pose)
Use control law and dock perception to approach the charge dock.
void getPreemptedGoalIfRequested(typename std::shared_ptr< const typename ActionT::Goal > goal, const std::unique_ptr< nav2_util::SimpleActionServer< ActionT >> &action_server)
Gets a preempted goal if immediately requested.
void publishZeroVelocity()
Publish zero velocity at terminal condition.
void undockRobot()
Main action callback method to complete undocking request.
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configure member variables.
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activate member variables.
void stashDockData(bool use_dock_id, Dock *dock, bool successful)
Called at the conclusion of docking actions. Saves relevant docking data for later undocking action.
bool checkAndWarnIfCancelled(std::unique_ptr< nav2_util::SimpleActionServer< ActionT >> &action_server, const std::string &name)
Checks and logs warning if action canceled.
Dock was not found in the provided dock database.
Dock plugin provided in the database or action was invalid.
Failed to control into or out of the dock.
Failed to detect the charging dock.
Failed to navigate to the staging pose.
Definition: types.hpp:33