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"
21 using namespace std::chrono_literals;
22 using rcl_interfaces::msg::ParameterType;
23 using std::placeholders::_1;
25 namespace opennav_docking
28 DockingServer::DockingServer(
const rclcpp::NodeOptions & options)
29 : nav2::LifecycleNode(
"docking_server",
"", options)
31 RCLCPP_INFO(get_logger(),
"Creating %s", get_name());
37 RCLCPP_INFO(get_logger(),
"Configuring %s", get_name());
39 param_handler_ = std::make_unique<ParameterHandler>(
41 params_ = param_handler_->getParams();
43 vel_publisher_ = std::make_unique<nav2_util::TwistPublisher>(node,
"cmd_vel");
44 tf2_buffer_ = nav2::create_transform_buffer(node);
47 odom_sub_ = std::make_unique<nav2_util::OdomSmoother>(node, params_->odom_duration,
51 docking_action_server_ = node->create_action_server<DockRobot>(
54 nullptr,
nullptr, std::chrono::milliseconds(500),
57 undocking_action_server_ = node->create_action_server<UndockRobot>(
60 nullptr,
nullptr, std::chrono::milliseconds(500),
64 controller_ = std::make_unique<Controller>(node, tf2_buffer_, params_->fixed_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_)) {
70 return nav2::CallbackReturn::FAILURE;
73 return nav2::CallbackReturn::SUCCESS;
79 RCLCPP_INFO(get_logger(),
"Activating %s", get_name());
83 tf2_listener_ = nav2::create_transform_listener(*tf2_buffer_,
this,
true);
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();
95 return nav2::CallbackReturn::SUCCESS;
101 RCLCPP_INFO(get_logger(),
"Deactivating %s", get_name());
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();
114 return nav2::CallbackReturn::SUCCESS;
120 RCLCPP_INFO(get_logger(),
"Cleaning up %s", get_name());
122 docking_action_server_.reset();
123 undocking_action_server_.reset();
126 curr_dock_type_.clear();
128 vel_publisher_.reset();
129 params_->dock_backwards.reset();
131 return nav2::CallbackReturn::SUCCESS;
137 RCLCPP_INFO(get_logger(),
"Shutting down %s", get_name());
138 return nav2::CallbackReturn::SUCCESS;
141 template<
typename ActionT>
143 typename std::shared_ptr<const typename ActionT::Goal> goal,
144 const typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
151 template<
typename ActionT>
153 typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
154 const std::string & name)
157 RCLCPP_WARN(get_logger(),
"Goal was cancelled. Cancelling %s action", name.c_str());
163 template<
typename ActionT>
165 typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
166 const std::string & name)
169 RCLCPP_WARN(get_logger(),
"Goal was preempted. Cancelling %s action", name.c_str());
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);
181 auto goal = docking_action_server_->get_current_goal();
182 auto result = std::make_shared<DockRobot::Result>();
183 result->success =
false;
185 if (!docking_action_server_ || !docking_action_server_->is_server_active()) {
186 RCLCPP_DEBUG(get_logger(),
"Action server unavailable or inactive. Stopping.");
190 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot")) {
191 docking_action_server_->terminate_all();
195 getPreemptedGoalIfRequested<DockRobot>(goal, docking_action_server_);
196 Dock * dock{
nullptr};
201 if (goal->use_dock_id) {
204 "Attempting to dock robot at %s.", goal->dock_id.c_str());
205 dock = dock_db_->findDock(goal->dock_id);
209 "Attempting to dock robot at position (%0.2f, %0.2f).",
210 goal->dock_pose.pose.position.x, goal->dock_pose.pose.position.y);
215 if (dock->plugin->isCharger() && (dock->plugin->isDocked() || dock->plugin->isCharging())) {
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);
225 const auto initial_staging_pose = dock->getStagingPose();
227 if (!goal->navigate_to_staging_pose ||
228 utils::l2Norm(robot_pose.pose,
229 initial_staging_pose.pose) < params_->dock_prestaging_tolerance)
231 RCLCPP_INFO(get_logger(),
"Robot already within pre-staging pose tolerance for dock");
233 std::function<bool()> isPreempted = [
this]() {
234 return checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
235 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot");
238 navigator_->goToPose(
239 initial_staging_pose,
240 rclcpp::Duration::from_seconds(goal->max_staging_time),
242 RCLCPP_INFO(get_logger(),
"Successful navigation to staging pose");
246 auto dock_pose = utils::getDockPoseStamped(dock, rclcpp::Time(0));
247 tf2_buffer_->transform(dock_pose, dock_pose, params_->fixed_frame);
251 RCLCPP_INFO(get_logger(),
"Successful initial dock detection");
254 bool dock_backward = params_->dock_backwards.has_value() ?
255 params_->dock_backwards.value() :
256 (dock->plugin->getDockDirection() == opennav_docking_core::DockDirection::BACKWARD);
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);
267 rclcpp::Time dock_contact_time;
268 while (rclcpp::ok()) {
271 if (dock->plugin->shouldRotateToDock()) {
278 get_logger(),
"Made contact with dock, waiting for charge to start (if applicable).");
281 if (dock->plugin->isCharger()) {
282 RCLCPP_INFO(get_logger(),
"Robot is charging!");
284 RCLCPP_INFO(get_logger(),
"Docking was successful!");
286 result->success =
true;
287 result->num_retries = num_retries_;
290 dock->plugin->stopDetectionProcess();
291 docking_action_server_->succeeded_current(result);
299 dock->plugin->stopDetectionProcess();
300 docking_action_server_->terminate_all(result);
303 if (++num_retries_ > params_->max_retries) {
304 RCLCPP_ERROR(get_logger(),
"Failed to dock, all retries have been used");
307 RCLCPP_WARN(get_logger(),
"Docking failed, will retry: %s", e.what());
315 dock->plugin->stopDetectionProcess();
316 docking_action_server_->terminate_all(result);
319 RCLCPP_INFO(get_logger(),
"Returned to staging pose, attempting docking again");
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;
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;
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());
361 result->num_retries = num_retries_;
363 dock->plugin->stopDetectionProcess();
364 docking_action_server_->terminate_current(result);
369 if (dock && successful) {
370 curr_dock_type_ = dock->type;
373 if (!use_dock_id && dock) {
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);
393 if (!dock->plugin->startDetectionProcess()) {
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");
406 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
407 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
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);
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);
427 while (rclcpp::ok()) {
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) {
435 auto current_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
436 current_vel->twist.angular.z = odom_sub_->getRawTwist().angular.z;
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);
443 vel_publisher_->publish(std::move(command));
445 if (this->now() - start > timeout) {
454 Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose,
bool backward)
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);
460 while (rclcpp::ok()) {
464 if (dock->plugin->isDocked() || (dock->plugin->isCharger() && dock->plugin->isCharging())) {
469 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
470 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
476 if (!dock->plugin->getRefinedPose(dock_pose, dock->id) && !dock->plugin->shouldRotateToDock()) {
481 geometry_msgs::msg::PoseStamped target_pose = dock_pose;
482 target_pose.header.stamp = rclcpp::Time(0);
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);
497 target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
498 tf2::getYaw(target_pose.pose.orientation) + M_PI);
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)) {
507 vel_publisher_->publish(std::move(command));
509 if (this->now() - start > timeout) {
511 "Timed out approaching dock; dock nor charging (if applicable) detected");
522 if (!dock->plugin->isCharger()) {
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()) {
532 if (dock->plugin->isCharging()) {
536 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
537 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
542 if (this->now() - start > timeout) {
552 const geometry_msgs::msg::PoseStamped & staging_pose,
bool backward)
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()) {
561 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
562 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
568 auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
569 command->header.stamp = now();
571 command->twist, staging_pose, params_->undock_linear_tolerance,
572 params_->undock_angular_tolerance,
false,
577 vel_publisher_->publish(std::move(command));
579 if (this->now() - start > timeout) {
589 geometry_msgs::msg::Twist & cmd,
const geometry_msgs::msg::PoseStamped & pose,
590 double linear_tolerance,
double angular_tolerance,
bool is_docking,
bool backward)
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) {
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);
613 if (!controller_->computeVelocityCommand(target_pose.pose, cmd, is_docking, backward)) {
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);
627 auto goal = undocking_action_server_->get_current_goal();
628 auto result = std::make_shared<UndockRobot::Result>();
629 result->success =
false;
631 if (!undocking_action_server_ || !undocking_action_server_->is_server_active()) {
632 RCLCPP_DEBUG(get_logger(),
"Action server unavailable or inactive. Stopping.");
636 if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_,
"undock_robot")) {
637 undocking_action_server_->terminate_all(result);
641 getPreemptedGoalIfRequested<UndockRobot>(goal, undocking_action_server_);
642 auto max_duration = rclcpp::Duration::from_seconds(goal->max_undocking_time);
646 std::string dock_type = curr_dock_type_;
647 if (!goal->dock_type.empty()) {
648 dock_type = goal->dock_type;
651 ChargingDock::Ptr dock = dock_db_->findDockPlugin(dock_type);
657 "Attempting to undock robot of dock type %s.", dock->getName().c_str());
660 if (dock->isCharger() && (!dock->isDocked() && !dock->isCharging())) {
661 RCLCPP_INFO(get_logger(),
"Robot is not in the dock, no need to undock");
665 bool dock_backward = params_->dock_backwards.has_value() ?
666 params_->dock_backwards.value() :
667 (dock->getDockDirection() == opennav_docking_core::DockDirection::BACKWARD);
674 dock_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
675 tf2::getYaw(dock_pose.pose.orientation) + M_PI);
679 geometry_msgs::msg::PoseStamped staging_pose =
680 dock->getStagingPose(dock_pose.pose, dock_pose.header.frame_id);
684 if (dock->shouldRotateToDock()) {
685 staging_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
686 tf2::getYaw(staging_pose.pose.orientation) + M_PI);
690 rclcpp::Time loop_start = this->now();
691 while (rclcpp::ok()) {
693 auto timeout = rclcpp::Duration::from_seconds(goal->max_undocking_time);
694 if (this->now() - loop_start > timeout) {
699 if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_,
"undock_robot") ||
700 checkAndWarnIfPreempted<UndockRobot>(undocking_action_server_,
"undock_robot"))
703 undocking_action_server_->terminate_all(result);
708 if (dock->isCharger() && !dock->disableCharging()) {
714 auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
715 command->header.stamp = now();
718 command->twist, staging_pose, params_->undock_linear_tolerance,
719 params_->undock_angular_tolerance,
false,
723 if (dock->shouldRotateToDock()) {
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);
743 vel_publisher_->publish(std::move(command));
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;
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;
769 undocking_action_server_->terminate_current(result);
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);
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));
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);
798 #include "rclcpp_components/register_node_macro.hpp"
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.
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.
Abstract docking exception.
Failed to start charging.
Failed to control into or out of the dock.
Failed to detect the charging dock.
Failed to navigate to the staging pose.