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