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