Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
docking_panel.cpp
1 // Copyright (c) 2024 Alberto J. Tudela Roldán
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 // QT
16 #include <QLineEdit>
17 #include <QVBoxLayout>
18 #include <QHBoxLayout>
19 #include <QLabel>
20 
21 // C++
22 #include <chrono>
23 #include <memory>
24 #include <sstream>
25 #include <string>
26 
27 #include <rclcpp/rclcpp.hpp>
28 #include <rviz_common/display_context.hpp>
29 
30 #include "nav2_util/geometry_utils.hpp"
31 #include "nav2_rviz_plugins/docking_panel.hpp"
32 #include "nav2_rviz_plugins/ros_action_qevent.hpp"
33 #include "nav2_rviz_plugins/utils.hpp"
34 
35 using namespace std::chrono_literals;
36 
37 namespace nav2_rviz_plugins
38 {
39 
40 DockingPanel::DockingPanel(QWidget * parent)
41 : Panel(parent),
42  server_timeout_(100)
43 {
44  // Create the control buttons and its tooltip
45  main_layout_ = new QVBoxLayout;
46  info_layout_ = new QHBoxLayout;
47  feedback_layout_ = new QVBoxLayout;
48  dock_id_layout_ = new QHBoxLayout;
49  dock_type_layout_ = new QHBoxLayout;
50  dock_pose_layout_ = new QHBoxLayout;
51  nav_stage_layout_ = new QHBoxLayout;
52  dock_type_ = new QComboBox;
53  docking_button_ = new QPushButton;
54  undocking_button_ = new QPushButton;
55  docking_goal_status_indicator_ = new QLabel;
56  docking_feedback_indicator_ = new QLabel;
57  docking_result_indicator_ = new QLabel;
58  use_dock_id_checkbox_ = new QCheckBox;
59  nav_to_staging_checkbox_ = new QCheckBox;
60  dock_id_ = new QLineEdit;
61  dock_pose_x_ = new QLineEdit;
62  dock_pose_y_ = new QLineEdit;
63  dock_pose_yaw_ = new QLineEdit;
64 
65  // Create the state machine used to present the proper control button states in the UI
66  const char * nav_to_stage_msg = "Navigate to the staging pose before docking";
67  const char * use_dock_id_msg = "Use the dock id or the dock pose to dock the robot";
68  const char * dock_msg = "Dock the robot at the specified docking station";
69  const char * undock_msg = "Undock the robot from the docking station";
70  const char * cancel_dock_msg = "Cancel the current docking action";
71  const char * cancel_undock_msg = "Cancel the current undocking action";
72 
73  docking_goal_status_indicator_->setText(nav2_rviz_plugins::getGoalStatusLabel());
74  docking_feedback_indicator_->setText(getDockFeedbackLabel());
75  docking_goal_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
76  docking_feedback_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
77 
78  pre_initial_ = new QState();
79  pre_initial_->setObjectName("pre_initial");
80  pre_initial_->assignProperty(docking_button_, "text", "Dock robot");
81  pre_initial_->assignProperty(docking_button_, "enabled", false);
82 
83  pre_initial_->assignProperty(undocking_button_, "text", "Undock robot");
84  pre_initial_->assignProperty(undocking_button_, "enabled", false);
85 
86  pre_initial_->assignProperty(nav_to_staging_checkbox_, "enabled", false);
87  pre_initial_->assignProperty(nav_to_staging_checkbox_, "checked", true);
88  pre_initial_->assignProperty(use_dock_id_checkbox_, "enabled", false);
89  pre_initial_->assignProperty(use_dock_id_checkbox_, "checked", true);
90  pre_initial_->assignProperty(dock_id_, "enabled", false);
91  pre_initial_->assignProperty(dock_type_, "enabled", false);
92  pre_initial_->assignProperty(dock_pose_x_, "enabled", false);
93  pre_initial_->assignProperty(dock_pose_y_, "enabled", false);
94  pre_initial_->assignProperty(dock_pose_yaw_, "enabled", false);
95 
96  // State entered when the docking / undocking action is not active
97  idle_ = new QState();
98  idle_->setObjectName("idle");
99  idle_->assignProperty(docking_button_, "text", "Dock robot");
100  idle_->assignProperty(docking_button_, "toolTip", dock_msg);
101  idle_->assignProperty(docking_button_, "enabled", true);
102 
103  idle_->assignProperty(undocking_button_, "text", "Undock robot");
104  idle_->assignProperty(undocking_button_, "toolTip", undock_msg);
105  idle_->assignProperty(undocking_button_, "enabled", true);
106 
107  idle_->assignProperty(nav_to_staging_checkbox_, "enabled", true);
108  idle_->assignProperty(nav_to_staging_checkbox_, "toolTip", nav_to_stage_msg);
109  idle_->assignProperty(use_dock_id_checkbox_, "enabled", true);
110  idle_->assignProperty(use_dock_id_checkbox_, "toolTip", use_dock_id_msg);
111  idle_->assignProperty(dock_id_, "enabled", true);
112  idle_->assignProperty(dock_type_, "enabled", true);
113 
114  // State entered to cancel the docking action
115  canceled_docking_ = new QState();
116  canceled_docking_->setObjectName("canceled_docking");
117 
118  // State entered to cancel the undocking action
119  canceled_undocking_ = new QState();
120  canceled_undocking_->setObjectName("canceled_undocking");
121 
122  // State entered while the docking action is active
123  docking_ = new QState();
124  docking_->setObjectName("docking");
125  docking_->assignProperty(docking_button_, "text", "Cancel docking");
126  docking_->assignProperty(docking_button_, "toolTip", cancel_dock_msg);
127 
128  docking_->assignProperty(undocking_button_, "enabled", false);
129 
130  // State entered while the undocking action is active
131  undocking_ = new QState();
132  undocking_->setObjectName("undocking");
133  undocking_->assignProperty(docking_button_, "enabled", false);
134 
135  undocking_->assignProperty(undocking_button_, "text", "Cancel undocking");
136  undocking_->assignProperty(undocking_button_, "toolTip", cancel_undock_msg);
137 
138  QObject::connect(docking_, SIGNAL(entered()), this, SLOT(onDockingButtonPressed()));
139  QObject::connect(undocking_, SIGNAL(entered()), this, SLOT(onUndockingButtonPressed()));
140  QObject::connect(canceled_docking_, SIGNAL(exited()), this, SLOT(onCancelDocking()));
141  QObject::connect(canceled_undocking_, SIGNAL(exited()), this, SLOT(onCancelUndocking()));
142 
143  // Start/Cancel button click transitions
144  idle_->addTransition(docking_button_, SIGNAL(clicked()), docking_);
145  idle_->addTransition(undocking_button_, SIGNAL(clicked()), undocking_);
146  docking_->addTransition(docking_button_, SIGNAL(clicked()), canceled_docking_);
147  undocking_->addTransition(undocking_button_, SIGNAL(clicked()), canceled_undocking_);
148 
149  // Internal state transitions
150  canceled_docking_->addTransition(canceled_docking_, SIGNAL(entered()), idle_);
151  canceled_undocking_->addTransition(canceled_undocking_, SIGNAL(entered()), idle_);
152 
153  // ROSAction Transitions: So when actions are updated remotely (failing, succeeding, etc)
154  // the state of the application will also update. This means that if in the processing
155  // states and then goes inactive, move back to the idle state. Vice versa as well.
156  ROSActionQTransition * idleDockTransition = new ROSActionQTransition(QActionState::INACTIVE);
157  idleDockTransition->setTargetState(docking_);
158  idle_->addTransition(idleDockTransition);
159 
160  ROSActionQTransition * idleUndockTransition = new ROSActionQTransition(QActionState::INACTIVE);
161  idleUndockTransition->setTargetState(undocking_);
162  idle_->addTransition(idleUndockTransition);
163 
164  ROSActionQTransition * dockingTransition = new ROSActionQTransition(QActionState::ACTIVE);
165  dockingTransition->setTargetState(idle_);
166  docking_->addTransition(dockingTransition);
167 
168  ROSActionQTransition * undockingTransition = new ROSActionQTransition(QActionState::ACTIVE);
169  undockingTransition->setTargetState(idle_);
170  undocking_->addTransition(undockingTransition);
171 
172  client_node_ = std::make_shared<rclcpp::Node>("nav2_rviz_docking_panel_node"); // nosemgrep
173  executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
174  executor_->add_node(client_node_);
175 
176  state_machine_.addState(pre_initial_);
177  state_machine_.addState(idle_);
178  state_machine_.addState(docking_);
179  state_machine_.addState(undocking_);
180  state_machine_.addState(canceled_docking_);
181  state_machine_.addState(canceled_undocking_);
182 
183  state_machine_.setInitialState(pre_initial_);
184 
185  // Delay starting initial thread until state machine has started or a race occurs
186  QObject::connect(&state_machine_, SIGNAL(started()), this, SLOT(startThread()));
187  state_machine_.start();
188 
189  // Create the layout for the panel
190  info_layout_->addWidget(docking_goal_status_indicator_);
191  info_layout_->addWidget(docking_result_indicator_);
192  feedback_layout_->addWidget(docking_feedback_indicator_);
193 
194  QLabel * nav_stage_label = new QLabel("Nav. to staging pose");
195  QLabel * dock_id_label = new QLabel("Dock id");
196  QLabel * dock_type_label = new QLabel("Dock type");
197 
198  nav_stage_label->setFixedWidth(150);
199  dock_id_label->setFixedWidth(150);
200  dock_type_label->setFixedWidth(170);
201 
202  nav_stage_layout_->addWidget(nav_stage_label);
203  nav_stage_layout_->addWidget(nav_to_staging_checkbox_);
204  dock_id_layout_->addWidget(dock_id_label);
205  dock_id_layout_->addWidget(use_dock_id_checkbox_);
206  dock_id_layout_->addWidget(dock_id_);
207  dock_type_layout_->addWidget(dock_type_label);
208  dock_type_layout_->addWidget(dock_type_);
209  dock_pose_layout_->addWidget(new QLabel("Dock pose {X"));
210  dock_pose_layout_->addWidget(dock_pose_x_);
211  dock_pose_layout_->addWidget(new QLabel("Y"));
212  dock_pose_layout_->addWidget(dock_pose_y_);
213  dock_pose_layout_->addWidget(new QLabel("θ"));
214  dock_pose_layout_->addWidget(dock_pose_yaw_);
215  dock_pose_layout_->addWidget(new QLabel("}"));
216 
217  QGroupBox * group_box = new QGroupBox();
218  QVBoxLayout * group_box_layout = new QVBoxLayout;
219  group_box_layout->addLayout(nav_stage_layout_);
220  group_box_layout->addLayout(dock_id_layout_);
221  group_box_layout->addLayout(dock_type_layout_);
222  group_box_layout->addLayout(dock_pose_layout_);
223  group_box->setLayout(group_box_layout);
224 
225  main_layout_->setContentsMargins(10, 10, 10, 10);
226  main_layout_->addLayout(info_layout_);
227  main_layout_->addLayout(feedback_layout_);
228  main_layout_->addWidget(group_box);
229  main_layout_->addWidget(docking_button_);
230  main_layout_->addWidget(undocking_button_);
231 
232  setLayout(main_layout_);
233  action_timer_.start(200, this);
234 
235  dock_client_ = rclcpp_action::create_client<Dock>(client_node_, "dock_robot"); // nosemgrep
236  // nosemgrep
237  undock_client_ = rclcpp_action::create_client<Undock>(client_node_, "undock_robot");
238  initial_thread_ = new InitialDockThread(dock_client_, undock_client_);
239  connect(initial_thread_, &InitialDockThread::finished, initial_thread_, &QObject::deleteLater);
240 
241  QSignalTransition * activeDockSignal = new QSignalTransition(
242  initial_thread_, &InitialDockThread::dockingActive);
243  activeDockSignal->setTargetState(idle_);
244  pre_initial_->addTransition(activeDockSignal);
245 
246  QSignalTransition * activeUndockSignal = new QSignalTransition(
247  initial_thread_, &InitialDockThread::undockingActive);
248  activeUndockSignal->setTargetState(idle_);
249  pre_initial_->addTransition(activeUndockSignal);
250 
251  QObject::connect(
252  initial_thread_, &InitialDockThread::dockingActive,
253  [this] {
254  // Load the plugins if not already loaded
255  if (!plugins_loaded_) {
256  RCLCPP_INFO(client_node_->get_logger(), "Loading dock plugins");
257  nav2_rviz_plugins::pluginLoader(
258  client_node_, server_failed_, "docking_server", "dock_plugins", dock_type_, executor_);
259  plugins_loaded_ = true;
260  }
261  });
262 
263  // Connect buttons with functions
264  #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 2)
265  QObject::connect(
266  use_dock_id_checkbox_, &QCheckBox::checkStateChanged, this, &DockingPanel::dockIdCheckbox);
267  #else
268  QObject::connect(
269  use_dock_id_checkbox_, &QCheckBox::stateChanged, this, &DockingPanel::dockIdCheckbox);
270  #endif
271 }
272 
273 void DockingPanel::onInitialize()
274 {
275  node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
276  if (node_ptr_ == nullptr) {
277  // The node no longer exists, so just don't initialize
278  RCLCPP_ERROR(
279  rclcpp::get_logger("docking_panel"),
280  "Underlying ROS node no longer exists, initialization failed");
281  return;
282  }
283  rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node(); // nosemgrep
284 
285  // Create action feedback subscriber
286  docking_feedback_sub_ = node->create_subscription<Dock::Impl::FeedbackMessage>(
287  "dock_robot/_action/feedback",
288  rclcpp::SystemDefaultsQoS(),
289  [this](const Dock::Impl::FeedbackMessage::ConstSharedPtr & msg) {
290  docking_feedback_indicator_->setText(getDockFeedbackLabel(msg->feedback));
291  });
292 
293  // Create action goal status subscribers
294  docking_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
295  "dock_robot/_action/status",
296  rclcpp::SystemDefaultsQoS(),
297  [this](const action_msgs::msg::GoalStatusArray::ConstSharedPtr & msg) {
298  docking_goal_status_indicator_->setText(
299  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
300  // Reset values when action is completed
301  if (msg->status_list.back().status == action_msgs::msg::GoalStatus::STATUS_SUCCEEDED) {
302  docking_feedback_indicator_->setText(getDockFeedbackLabel());
303  }
304  });
305 
306  undocking_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
307  "undock_robot/_action/status",
308  rclcpp::SystemDefaultsQoS(),
309  [this](const action_msgs::msg::GoalStatusArray::ConstSharedPtr & msg) {
310  docking_goal_status_indicator_->setText(
311  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
312  });
313 }
314 
315 void DockingPanel::startThread()
316 {
317  // start initial thread now that state machine is started
318  initial_thread_->start();
319 }
320 
321 DockingPanel::~DockingPanel()
322 {
323 }
324 
325 void DockingPanel::load(const rviz_common::Config & config)
326 {
327  Panel::load(config);
328 }
329 
330 void DockingPanel::save(rviz_common::Config config) const
331 {
332  Panel::save(config);
333 }
334 
335 void DockingPanel::onDockingButtonPressed()
336 {
337  auto is_action_server_ready =
338  dock_client_->wait_for_action_server(std::chrono::seconds(5));
339  if (!is_action_server_ready) {
340  RCLCPP_ERROR(client_node_->get_logger(), "dock_robot action server is not available.");
341  return;
342  }
343 
344  QComboBox * combo_box = dock_type_;
345  // If "default" option is selected, it gets removed and the next item is selected
346  if (combo_box->findText("Default") != -1) {
347  combo_box->removeItem(0);
348  }
349 
350  // If there are no plugins available, return
351  if (combo_box->count() == 0) {
352  return;
353  }
354 
355  // Send the goal to the action server
356  auto goal_msg = Dock::Goal();
357  goal_msg.use_dock_id = use_dock_id_;
358  goal_msg.navigate_to_staging_pose = nav_to_staging_checkbox_->isChecked();
359  if (use_dock_id_) {
360  if (dock_id_->text().isEmpty()) {
361  RCLCPP_ERROR(client_node_->get_logger(), "Dock id is empty.");
362  return;
363  }
364  goal_msg.dock_id = dock_id_->text().toStdString();
365 
366  RCLCPP_INFO(
367  client_node_->get_logger(), "DockRobot will be called using dock id: %s",
368  goal_msg.dock_id.c_str());
369 
370  } else {
371  if (dock_pose_x_->text().isEmpty() || dock_pose_y_->text().isEmpty() ||
372  dock_pose_yaw_->text().isEmpty())
373  {
374  RCLCPP_ERROR(client_node_->get_logger(), "Dock pose is empty.");
375  return;
376  }
377  goal_msg.dock_pose.header.frame_id = "map";
378  goal_msg.dock_pose.header.stamp = client_node_->now();
379  goal_msg.dock_pose.pose.position.x = dock_pose_x_->text().toDouble();
380  goal_msg.dock_pose.pose.position.y = dock_pose_y_->text().toDouble();
381  goal_msg.dock_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
382  dock_pose_yaw_->text().toDouble());
383  goal_msg.dock_type = combo_box->currentText().toStdString();
384 
385  RCLCPP_INFO(
386  client_node_->get_logger(), "DockRobot will be called using dock pose: (%f, %f) and type: %s",
387  goal_msg.dock_pose.pose.position.x, goal_msg.dock_pose.pose.position.y,
388  goal_msg.dock_type.c_str());
389  }
390 
391  // Enable result awareness by providing an empty lambda function
392  auto send_goal_options = nav2::ActionClient<Dock>::SendGoalOptions();
393  send_goal_options.result_callback = [this](const DockGoalHandle::WrappedResult & result) {
394  dock_goal_handle_.reset();
395  if (result.result->success) {
396  docking_result_indicator_->setText("");
397  } else {
398  docking_result_indicator_->setText(
399  QString(std::string("(" + dockErrorToString(result.result->error_code) + ")").c_str()));
400  }
401  };
402 
403  auto future_goal_handle = dock_client_->async_send_goal(goal_msg, send_goal_options);
404  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
405  rclcpp::FutureReturnCode::SUCCESS)
406  {
407  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
408  return;
409  }
410 
411  // Get the goal handle and save so that we can check on completion in the timer callback
412  dock_goal_handle_ = future_goal_handle.get();
413  if (!dock_goal_handle_) {
414  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
415  return;
416  }
417 
418  action_timer_.start(200, this);
419 }
420 
421 void DockingPanel::onUndockingButtonPressed()
422 {
423  auto is_action_server_ready =
424  undock_client_->wait_for_action_server(std::chrono::seconds(5));
425  if (!is_action_server_ready) {
426  RCLCPP_ERROR(client_node_->get_logger(), "undock_robot action server is not available.");
427  return;
428  }
429 
430  QComboBox * combo_box = dock_type_;
431  // If "default" option is selected, it gets removed and the next item is selected
432  if (combo_box->findText("Default") != -1) {
433  combo_box->removeItem(0);
434  }
435 
436  // If there are no plugins available, return
437  if (combo_box->count() == 0) {
438  return;
439  }
440 
441  // Send the goal to the action server
442  auto goal_msg = Undock::Goal();
443  goal_msg.dock_type = combo_box->currentText().toStdString();
444 
445  RCLCPP_INFO(
446  client_node_->get_logger(), "UndockRobot will be called using dock type: %s",
447  goal_msg.dock_type.c_str());
448 
449  // Enable result awareness by providing an empty lambda function
450  auto send_goal_options = nav2::ActionClient<Undock>::SendGoalOptions();
451  send_goal_options.result_callback = [this](const UndockGoalHandle::WrappedResult & result) {
452  undock_goal_handle_.reset();
453  if (result.result->success) {
454  docking_result_indicator_->setText("");
455  } else {
456  docking_result_indicator_->setText(
457  QString(std::string("(" + dockErrorToString(result.result->error_code) + ")").c_str()));
458  }
459  };
460 
461  auto future_goal_handle = undock_client_->async_send_goal(goal_msg, send_goal_options);
462  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
463  rclcpp::FutureReturnCode::SUCCESS)
464  {
465  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
466  return;
467  }
468 
469  // Get the goal handle and save so that we can check on completion in the timer callback
470  undock_goal_handle_ = future_goal_handle.get();
471  if (!undock_goal_handle_) {
472  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
473  return;
474  }
475 
476  action_timer_.start(200, this);
477 }
478 
479 void DockingPanel::dockIdCheckbox()
480 {
481  if (use_dock_id_checkbox_->isChecked()) {
482  use_dock_id_ = true;
483  dock_id_->setEnabled(true);
484  dock_pose_x_->setEnabled(false);
485  dock_pose_y_->setEnabled(false);
486  dock_pose_yaw_->setEnabled(false);
487  } else {
488  use_dock_id_ = false;
489  dock_id_->setEnabled(false);
490  dock_pose_x_->setEnabled(true);
491  dock_pose_y_->setEnabled(true);
492  dock_pose_yaw_->setEnabled(true);
493  }
494 }
495 
496 void DockingPanel::onCancelDocking()
497 {
498  if (dock_goal_handle_) {
499  auto future_cancel = dock_client_->async_cancel_goal(dock_goal_handle_);
500 
501  if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
502  rclcpp::FutureReturnCode::SUCCESS)
503  {
504  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel goal");
505  } else {
506  dock_goal_handle_.reset();
507  }
508  }
509 
510  action_timer_.stop();
511 }
512 
513 void DockingPanel::onCancelUndocking()
514 {
515  if (undock_goal_handle_) {
516  auto future_cancel = undock_client_->async_cancel_goal(undock_goal_handle_);
517 
518  if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
519  rclcpp::FutureReturnCode::SUCCESS)
520  {
521  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel goal");
522  } else {
523  undock_goal_handle_.reset();
524  }
525  }
526 
527  action_timer_.stop();
528 }
529 
530 void DockingPanel::timerEvent(QTimerEvent * event)
531 {
532  if (event->timerId() == action_timer_.timerId()) {
533  // Check the status of the action servers
534  if (state_machine_.configuration().contains(docking_)) {
535  if (!dock_goal_handle_) {
536  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
537  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
538  return;
539  }
540 
541  executor_->spin_some();
542  auto status = dock_goal_handle_->get_status();
543 
544  // Check if the goal is still executing
545  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
546  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
547  {
548  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
549  } else {
550  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
551  action_timer_.stop();
552  }
553  } else if (state_machine_.configuration().contains(undocking_)) {
554  if (!undock_goal_handle_) {
555  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
556  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
557  return;
558  }
559 
560  executor_->spin_some();
561  auto status = undock_goal_handle_->get_status();
562 
563  // Check if the goal is still executing
564  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
565  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
566  {
567  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
568  } else {
569  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
570  action_timer_.stop();
571  }
572  }
573  }
574 }
575 
576 inline QString DockingPanel::getDockFeedbackLabel(Dock::Feedback msg)
577 {
578  return QString(std::string("<table>" + toLabel(msg) + "</table>").c_str());
579 }
580 
581 template<typename T>
582 inline std::string DockingPanel::toLabel(T & msg)
583 {
584  return std::string(
585  "</td></tr><tr><td width=150>State:</td><td>" +
586  dockStateToString(msg.state) +
587  "</td></tr><tr><td width=150>Time taken:</td><td>" +
588  toString(rclcpp::Duration(msg.docking_time).seconds(), 0) + " s"
589  "</td></tr><tr><td width=150>Retries:</td><td>" +
590  std::to_string(msg.num_retries) +
591  "</td></tr>");
592 }
593 
594 inline std::string DockingPanel::toString(double val, int precision)
595 {
596  std::ostringstream out;
597  out.precision(precision);
598  out << std::fixed << val;
599  return out.str();
600 }
601 
602 inline std::string DockingPanel::dockStateToString(int16_t state)
603 {
604  switch (state) {
605  case 0:
606  return "none";
607  case 1:
608  return "nav. to staging pose";
609  case 2:
610  return "initial perception";
611  case 3:
612  return "controlling";
613  case 4:
614  return "wait for charge";
615  case 5:
616  return "retry";
617  default:
618  return "none";
619  }
620 }
621 
622 inline std::string DockingPanel::dockErrorToString(int16_t error_code)
623 {
624  switch (error_code) {
625  case 0:
626  return "none";
627  case 901:
628  return "dock not in database";
629  case 902:
630  return "dock not valid";
631  case 903:
632  return "failed to stage";
633  case 904:
634  return "failed to detect dock";
635  case 905:
636  return "failed to control";
637  case 906:
638  return "failed to charge";
639  case 999:
640  default:
641  return "unknown";
642  }
643 }
644 
645 } // namespace nav2_rviz_plugins
646 
647 #include <pluginlib/class_list_macros.hpp> // NOLINT
648 PLUGINLIB_EXPORT_CLASS(nav2_rviz_plugins::DockingPanel, rviz_common::Panel)
Panel to interface to the docking server.