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