Nav2 Navigation Stack - lyrical  lyrical
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_;
288  stashDockData(goal->use_dock_id, dock, true);
290  dock->plugin->stopDetectionProcess();
291  docking_action_server_->succeeded_current(result);
292  return;
293  }
294  }
295 
296  // Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
297  stashDockData(goal->use_dock_id, dock, false);
299  dock->plugin->stopDetectionProcess();
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
313  stashDockData(goal->use_dock_id, dock, false);
315  dock->plugin->stopDetectionProcess();
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  // Store dock state for later undocking and delete temp dock, if applicable
360  stashDockData(goal->use_dock_id, dock, false);
361  result->num_retries = num_retries_;
363  dock->plugin->stopDetectionProcess();
364  docking_action_server_->terminate_current(result);
365 }
366 
367 void DockingServer::stashDockData(bool use_dock_id, Dock * dock, bool successful)
368 {
369  if (dock && successful) {
370  curr_dock_type_ = dock->type;
371  }
372 
373  if (!use_dock_id && dock) {
374  delete dock;
375  dock = nullptr;
376  }
377 }
378 
379 Dock * DockingServer::generateGoalDock(std::shared_ptr<const DockRobot::Goal> goal)
380 {
381  auto dock = new Dock();
382  dock->frame = goal->dock_pose.header.frame_id;
383  dock->pose = goal->dock_pose.pose;
384  dock->type = goal->dock_type;
385  dock->plugin = dock_db_->findDockPlugin(dock->type);
386  return dock;
387 }
388 
389 void DockingServer::doInitialPerception(Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose)
390 {
391  publishDockingFeedback(DockRobot::Feedback::INITIAL_PERCEPTION);
392 
393  if (!dock->plugin->startDetectionProcess()) {
394  throw opennav_docking_core::FailedToDetectDock("Failed to start the detection process.");
395  }
396 
397  nav2::Rate loop_rate(this, params_->controller_frequency);
398  auto start = this->now();
399  auto timeout = rclcpp::Duration::from_seconds(params_->initial_perception_timeout);
400  while (!dock->plugin->getRefinedPose(dock_pose, dock->id)) {
401  if (this->now() - start > timeout) {
403  "Failed initial dock detection: Timeout exceeded");
404  }
405 
406  if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_, "dock_robot") ||
407  checkAndWarnIfPreempted<DockRobot>(docking_action_server_, "dock_robot"))
408  {
409  return;
410  }
411 
412  loop_rate.sleep();
413  }
414 }
415 
416 void DockingServer::rotateToDock(const geometry_msgs::msg::PoseStamped & dock_pose)
417 {
418  const double dt = 1.0 / params_->controller_frequency;
419  auto target_pose = dock_pose;
420  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
421  tf2::getYaw(target_pose.pose.orientation) + M_PI);
422 
423  nav2::Rate loop_rate(this, params_->controller_frequency);
424  auto start = this->now();
425  auto timeout = rclcpp::Duration::from_seconds(params_->rotate_to_dock_timeout);
426 
427  while (rclcpp::ok()) {
428  auto robot_pose = getRobotPoseInFrame(dock_pose.header.frame_id);
429  auto angular_distance_to_heading = angles::shortest_angular_distance(
430  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(target_pose.pose.orientation));
431  if (fabs(angular_distance_to_heading) < params_->rotation_angular_tolerance) {
432  break;
433  }
434 
435  auto current_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
436  current_vel->twist.angular.z = odom_sub_->getRawTwist().angular.z;
437 
438  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
439  command->header = robot_pose.header;
440  command->twist = controller_->computeRotateToHeadingCommand(
441  angular_distance_to_heading, current_vel->twist, dt);
442 
443  vel_publisher_->publish(std::move(command));
444 
445  if (this->now() - start > timeout) {
446  throw opennav_docking_core::FailedToControl("Timed out rotating to dock");
447  }
448 
449  loop_rate.sleep();
450  }
451 }
452 
454  Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose, bool backward)
455 {
456  nav2::Rate loop_rate(this, params_->controller_frequency);
457  auto start = this->now();
458  auto timeout = rclcpp::Duration::from_seconds(params_->dock_approach_timeout);
459 
460  while (rclcpp::ok()) {
461  publishDockingFeedback(DockRobot::Feedback::CONTROLLING);
462 
463  // Stop and report success if connected to dock
464  if (dock->plugin->isDocked() || (dock->plugin->isCharger() && dock->plugin->isCharging())) {
465  return true;
466  }
467 
468  // Stop if cancelled/preempted
469  if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_, "dock_robot") ||
470  checkAndWarnIfPreempted<DockRobot>(docking_action_server_, "dock_robot"))
471  {
472  return false;
473  }
474 
475  // Update perception
476  if (!dock->plugin->getRefinedPose(dock_pose, dock->id) && !dock->plugin->shouldRotateToDock()) {
477  throw opennav_docking_core::FailedToDetectDock("Failed dock detection");
478  }
479 
480  // Transform target_pose into base_link frame
481  geometry_msgs::msg::PoseStamped target_pose = dock_pose;
482  target_pose.header.stamp = rclcpp::Time(0);
483 
484  // The control law can get jittery when close to the end when atan2's can explode.
485  // Thus, we backward project the controller's target pose a little bit after the
486  // dock so that the robot never gets to the end of the spiral before its in contact
487  // with the dock to stop the docking procedure.
488  const double backward_projection = 0.25;
489  const double yaw = tf2::getYaw(target_pose.pose.orientation);
490  target_pose.pose.position.x += cos(yaw) * backward_projection;
491  target_pose.pose.position.y += sin(yaw) * backward_projection;
492  tf2_buffer_->transform(target_pose, target_pose, params_->base_frame);
493 
494  // Make sure that the target pose is pointing at the robot when moving backwards
495  // This is to ensure that the robot doesn't try to dock from the wrong side
496  if (backward) {
497  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
498  tf2::getYaw(target_pose.pose.orientation) + M_PI);
499  }
500 
501  // Compute and publish controls
502  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
503  command->header.stamp = now();
504  if (!controller_->computeVelocityCommand(target_pose.pose, command->twist, true, backward)) {
505  throw opennav_docking_core::FailedToControl("Failed to get control");
506  }
507  vel_publisher_->publish(std::move(command));
508 
509  if (this->now() - start > timeout) {
511  "Timed out approaching dock; dock nor charging (if applicable) detected");
512  }
513 
514  loop_rate.sleep();
515  }
516  return false;
517 }
518 
520 {
521  // This is a non-charger docking request
522  if (!dock->plugin->isCharger()) {
523  return true;
524  }
525 
526  nav2::Rate loop_rate(this, params_->controller_frequency);
527  auto start = this->now();
528  auto timeout = rclcpp::Duration::from_seconds(params_->wait_charge_timeout);
529  while (rclcpp::ok()) {
530  publishDockingFeedback(DockRobot::Feedback::WAIT_FOR_CHARGE);
531 
532  if (dock->plugin->isCharging()) {
533  return true;
534  }
535 
536  if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_, "dock_robot") ||
537  checkAndWarnIfPreempted<DockRobot>(docking_action_server_, "dock_robot"))
538  {
539  return false;
540  }
541 
542  if (this->now() - start > timeout) {
543  throw opennav_docking_core::FailedToCharge("Timed out waiting for charge to start");
544  }
545 
546  loop_rate.sleep();
547  }
548  return false;
549 }
550 
552  const geometry_msgs::msg::PoseStamped & staging_pose, bool backward)
553 {
554  nav2::Rate loop_rate(this, params_->controller_frequency);
555  auto start = this->now();
556  auto timeout = rclcpp::Duration::from_seconds(params_->dock_approach_timeout);
557  while (rclcpp::ok()) {
558  publishDockingFeedback(DockRobot::Feedback::INITIAL_PERCEPTION);
559 
560  // Stop if cancelled/preempted
561  if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_, "dock_robot") ||
562  checkAndWarnIfPreempted<DockRobot>(docking_action_server_, "dock_robot"))
563  {
564  return false;
565  }
566 
567  // Compute and publish command
568  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
569  command->header.stamp = now();
570  if (getCommandToPose(
571  command->twist, staging_pose, params_->undock_linear_tolerance,
572  params_->undock_angular_tolerance, false,
573  !backward))
574  {
575  return true;
576  }
577  vel_publisher_->publish(std::move(command));
578 
579  if (this->now() - start > timeout) {
580  throw opennav_docking_core::FailedToControl("Timed out resetting dock approach");
581  }
582 
583  loop_rate.sleep();
584  }
585  return false;
586 }
587 
589  geometry_msgs::msg::Twist & cmd, const geometry_msgs::msg::PoseStamped & pose,
590  double linear_tolerance, double angular_tolerance, bool is_docking, bool backward)
591 {
592  // Reset command to zero velocity
593  cmd.linear.x = 0;
594  cmd.angular.z = 0;
595 
596  // Determine if we have reached pose yet & stop
597  geometry_msgs::msg::PoseStamped robot_pose = getRobotPoseInFrame(pose.header.frame_id);
598  const double dist = std::hypot(
599  robot_pose.pose.position.x - pose.pose.position.x,
600  robot_pose.pose.position.y - pose.pose.position.y);
601  const double yaw = angles::shortest_angular_distance(
602  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(pose.pose.orientation));
603  if (dist < linear_tolerance && abs(yaw) < angular_tolerance) {
604  return true;
605  }
606 
607  // Transform target_pose into base_link frame
608  geometry_msgs::msg::PoseStamped target_pose = pose;
609  target_pose.header.stamp = rclcpp::Time(0);
610  tf2_buffer_->transform(target_pose, target_pose, params_->base_frame);
611 
612  // Compute velocity command
613  if (!controller_->computeVelocityCommand(target_pose.pose, cmd, is_docking, backward)) {
614  throw opennav_docking_core::FailedToControl("Failed to get control");
615  }
616 
617  // Command is valid, but target is not reached
618  return false;
619 }
620 
622 {
623  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
624  action_start_time_ = this->now();
625  nav2::Rate loop_rate(this, params_->controller_frequency);
626 
627  auto goal = undocking_action_server_->get_current_goal();
628  auto result = std::make_shared<UndockRobot::Result>();
629  result->success = false;
630 
631  if (!undocking_action_server_ || !undocking_action_server_->is_server_active()) {
632  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
633  return;
634  }
635 
636  if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_, "undock_robot")) {
637  undocking_action_server_->terminate_all(result);
638  return;
639  }
640 
641  getPreemptedGoalIfRequested<UndockRobot>(goal, undocking_action_server_);
642  auto max_duration = rclcpp::Duration::from_seconds(goal->max_undocking_time);
643 
644  try {
645  // Get dock plugin information from request or docked state, reset state.
646  std::string dock_type = curr_dock_type_;
647  if (!goal->dock_type.empty()) {
648  dock_type = goal->dock_type;
649  }
650 
651  ChargingDock::Ptr dock = dock_db_->findDockPlugin(dock_type);
652  if (!dock) {
653  throw opennav_docking_core::DockNotValid("No dock information to undock from!");
654  }
655  RCLCPP_INFO(
656  get_logger(),
657  "Attempting to undock robot of dock type %s.", dock->getName().c_str());
658 
659  // Check if the robot is docked before proceeding
660  if (dock->isCharger() && (!dock->isDocked() && !dock->isCharging())) {
661  RCLCPP_INFO(get_logger(), "Robot is not in the dock, no need to undock");
662  return;
663  }
664 
665  bool dock_backward = params_->dock_backwards.has_value() ?
666  params_->dock_backwards.value() :
667  (dock->getDockDirection() == opennav_docking_core::DockDirection::BACKWARD);
668 
669  // Get "dock pose" by finding the robot pose
670  geometry_msgs::msg::PoseStamped dock_pose = getRobotPoseInFrame(params_->fixed_frame);
671 
672  // Make sure that the staging pose is pointing in the same direction when moving backwards
673  if (dock_backward) {
674  dock_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
675  tf2::getYaw(dock_pose.pose.orientation) + M_PI);
676  }
677 
678  // Get staging pose (in fixed frame)
679  geometry_msgs::msg::PoseStamped staging_pose =
680  dock->getStagingPose(dock_pose.pose, dock_pose.header.frame_id);
681 
682  // If we performed a rotation before docking backward, we must rotate the staging pose
683  // to match the robot orientation
684  if (dock->shouldRotateToDock()) {
685  staging_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
686  tf2::getYaw(staging_pose.pose.orientation) + M_PI);
687  }
688 
689  // Control robot to staging pose
690  rclcpp::Time loop_start = this->now();
691  while (rclcpp::ok()) {
692  // Stop if we exceed max duration
693  auto timeout = rclcpp::Duration::from_seconds(goal->max_undocking_time);
694  if (this->now() - loop_start > timeout) {
695  throw opennav_docking_core::FailedToControl("Undocking timed out");
696  }
697 
698  // Stop if cancelled/preempted
699  if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_, "undock_robot") ||
700  checkAndWarnIfPreempted<UndockRobot>(undocking_action_server_, "undock_robot"))
701  {
703  undocking_action_server_->terminate_all(result);
704  return;
705  }
706 
707  // Don't control the robot until charging is disabled
708  if (dock->isCharger() && !dock->disableCharging()) {
709  loop_rate.sleep();
710  continue;
711  }
712 
713  // Get command to approach staging pose
714  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
715  command->header.stamp = now();
716 
717  if (getCommandToPose(
718  command->twist, staging_pose, params_->undock_linear_tolerance,
719  params_->undock_angular_tolerance, false,
720  !dock_backward))
721  {
722  // Perform a 180º to the original staging pose
723  if (dock->shouldRotateToDock()) {
724  rotateToDock(staging_pose);
725  }
726 
727  // Have reached staging_pose
728  RCLCPP_INFO(get_logger(), "Robot has reached staging pose");
729  vel_publisher_->publish(std::move(command));
730  if (!dock->isCharger() || dock->hasStoppedCharging()) {
731  RCLCPP_INFO(get_logger(), "Robot has undocked!");
732  result->success = true;
733  curr_dock_type_.clear();
735  undocking_action_server_->succeeded_current(result);
736  return;
737  }
738  // Haven't stopped charging?
739  throw opennav_docking_core::FailedToControl("Failed to control off dock");
740  }
741 
742  // Publish command and sleep
743  vel_publisher_->publish(std::move(command));
744  loop_rate.sleep();
745  }
746  } catch (const tf2::TransformException & e) {
747  result->error_msg = std::string("Transform error: ") + e.what();
748  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
749  result->error_code = DockRobot::Result::UNKNOWN;
750  } catch (opennav_docking_core::DockNotValid & e) {
751  result->error_msg = e.what();
752  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
753  result->error_code = DockRobot::Result::DOCK_NOT_VALID;
755  result->error_msg = e.what();
756  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
757  result->error_code = DockRobot::Result::FAILED_TO_CONTROL;
759  result->error_msg = e.what();
760  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
761  result->error_code = DockRobot::Result::UNKNOWN;
762  } catch (std::exception & e) {
763  result->error_msg = std::string("Internal error: ") + e.what();
764  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
765  result->error_code = DockRobot::Result::UNKNOWN;
766  }
767 
769  undocking_action_server_->terminate_current(result);
770 }
771 
772 geometry_msgs::msg::PoseStamped DockingServer::getRobotPoseInFrame(const std::string & frame)
773 {
774  geometry_msgs::msg::PoseStamped robot_pose;
775  robot_pose.header.frame_id = params_->base_frame;
776  robot_pose.header.stamp = rclcpp::Time(0);
777  tf2_buffer_->transform(robot_pose, robot_pose, frame);
778  return robot_pose;
779 }
780 
782 {
783  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
784  cmd_vel->header.stamp = now();
785  vel_publisher_->publish(std::move(cmd_vel));
786 }
787 
789 {
790  auto feedback = std::make_shared<DockRobot::Feedback>();
791  feedback->state = state;
792  feedback->docking_time = this->now() - action_start_time_;
793  feedback->num_retries = num_retries_;
794  docking_action_server_->publish_feedback(feedback);
795 }
796 } // namespace opennav_docking
797 
798 #include "rclcpp_components/register_node_macro.hpp"
799 
800 // Register the component with class_loader.
801 // This acts as a sort of entry point, allowing the component to be discoverable when its library
802 // is being loaded into a running process.
803 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