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_;
289 dock->plugin->stopDetectionProcess();
291 docking_action_server_->succeeded_current(result);
298 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());
314 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());
359 result->num_retries = num_retries_;
362 dock->plugin->stopDetectionProcess();
366 docking_action_server_->terminate_current(result);
371 if (dock && successful) {
372 curr_dock_type_ = dock->type;
375 if (!use_dock_id && dock) {
383 auto plugin = dock_db_->findDockPlugin(goal->dock_type);
386 "Dock type '" + goal->dock_type +
"' has no valid plugin!");
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;
401 if (!dock->plugin->startDetectionProcess()) {
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");
414 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
415 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
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);
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);
435 while (rclcpp::ok()) {
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) {
443 auto current_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
444 current_vel->twist.angular.z = odom_sub_->getRawTwist().angular.z;
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);
451 vel_publisher_->publish(std::move(command));
453 if (this->now() - start > timeout) {
462 Dock * dock, geometry_msgs::msg::PoseStamped & dock_pose,
bool backward)
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);
468 while (rclcpp::ok()) {
472 if (dock->plugin->isDocked() || (dock->plugin->isCharger() && dock->plugin->isCharging())) {
477 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
478 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
484 if (!dock->plugin->getRefinedPose(dock_pose, dock->id) && !dock->plugin->shouldRotateToDock()) {
489 geometry_msgs::msg::PoseStamped target_pose = dock_pose;
490 target_pose.header.stamp = rclcpp::Time(0);
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);
505 target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
506 tf2::getYaw(target_pose.pose.orientation) + M_PI);
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)) {
515 vel_publisher_->publish(std::move(command));
517 if (this->now() - start > timeout) {
519 "Timed out approaching dock; dock nor charging (if applicable) detected");
530 if (!dock->plugin->isCharger()) {
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()) {
540 if (dock->plugin->isCharging()) {
544 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
545 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
550 if (this->now() - start > timeout) {
560 const geometry_msgs::msg::PoseStamped & staging_pose,
bool backward)
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()) {
569 if (checkAndWarnIfCancelled<DockRobot>(docking_action_server_,
"dock_robot") ||
570 checkAndWarnIfPreempted<DockRobot>(docking_action_server_,
"dock_robot"))
576 auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
577 command->header.stamp = now();
579 command->twist, staging_pose, params_->undock_linear_tolerance,
580 params_->undock_angular_tolerance,
false,
585 vel_publisher_->publish(std::move(command));
587 if (this->now() - start > timeout) {
597 geometry_msgs::msg::Twist & cmd,
const geometry_msgs::msg::PoseStamped & pose,
598 double linear_tolerance,
double angular_tolerance,
bool is_docking,
bool backward)
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) {
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);
621 if (!controller_->computeVelocityCommand(target_pose.pose, cmd, is_docking, backward)) {
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);
635 auto goal = undocking_action_server_->get_current_goal();
636 auto result = std::make_shared<UndockRobot::Result>();
637 result->success =
false;
639 if (!undocking_action_server_ || !undocking_action_server_->is_server_active()) {
640 RCLCPP_DEBUG(get_logger(),
"Action server unavailable or inactive. Stopping.");
644 if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_,
"undock_robot")) {
645 undocking_action_server_->terminate_all(result);
649 getPreemptedGoalIfRequested<UndockRobot>(goal, undocking_action_server_);
650 auto max_duration = rclcpp::Duration::from_seconds(goal->max_undocking_time);
654 std::string dock_type = curr_dock_type_;
655 if (!goal->dock_type.empty()) {
656 dock_type = goal->dock_type;
659 ChargingDock::Ptr dock = dock_db_->findDockPlugin(dock_type);
665 "Attempting to undock robot of dock type %s.", dock->getName().c_str());
668 if (dock->isCharger() && (!dock->isDocked() && !dock->isCharging())) {
669 RCLCPP_INFO(get_logger(),
"Robot is not in the dock, no need to undock");
673 bool dock_backward = params_->dock_backwards.has_value() ?
674 params_->dock_backwards.value() :
675 (dock->getDockDirection() == opennav_docking_core::DockDirection::BACKWARD);
682 dock_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
683 tf2::getYaw(dock_pose.pose.orientation) + M_PI);
687 geometry_msgs::msg::PoseStamped staging_pose =
688 dock->getStagingPose(dock_pose.pose, dock_pose.header.frame_id);
692 if (dock->shouldRotateToDock()) {
693 staging_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
694 tf2::getYaw(staging_pose.pose.orientation) + M_PI);
698 rclcpp::Time loop_start = this->now();
699 while (rclcpp::ok()) {
701 auto timeout = rclcpp::Duration::from_seconds(goal->max_undocking_time);
702 if (this->now() - loop_start > timeout) {
707 if (checkAndWarnIfCancelled<UndockRobot>(undocking_action_server_,
"undock_robot") ||
708 checkAndWarnIfPreempted<UndockRobot>(undocking_action_server_,
"undock_robot"))
711 undocking_action_server_->terminate_all(result);
716 if (dock->isCharger() && !dock->disableCharging()) {
722 auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
723 command->header.stamp = now();
726 command->twist, staging_pose, params_->undock_linear_tolerance,
727 params_->undock_angular_tolerance,
false,
731 if (dock->shouldRotateToDock()) {
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);
751 vel_publisher_->publish(std::move(command));
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;
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;
777 undocking_action_server_->terminate_current(result);
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);
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));
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);
806 #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.