17 #include <QVBoxLayout>
18 #include <QHBoxLayout>
27 #include <rclcpp/rclcpp.hpp>
28 #include <rviz_common/display_context.hpp>
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"
35 using namespace std::chrono_literals;
37 namespace nav2_rviz_plugins
40 DockingPanel::DockingPanel(QWidget * parent)
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;
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";
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);
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);
83 pre_initial_->assignProperty(undocking_button_,
"text",
"Undock robot");
84 pre_initial_->assignProperty(undocking_button_,
"enabled",
false);
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);
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);
103 idle_->assignProperty(undocking_button_,
"text",
"Undock robot");
104 idle_->assignProperty(undocking_button_,
"toolTip", undock_msg);
105 idle_->assignProperty(undocking_button_,
"enabled",
true);
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);
115 canceled_docking_ =
new QState();
116 canceled_docking_->setObjectName(
"canceled_docking");
119 canceled_undocking_ =
new QState();
120 canceled_undocking_->setObjectName(
"canceled_undocking");
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);
128 docking_->assignProperty(undocking_button_,
"enabled",
false);
131 undocking_ =
new QState();
132 undocking_->setObjectName(
"undocking");
133 undocking_->assignProperty(docking_button_,
"enabled",
false);
135 undocking_->assignProperty(undocking_button_,
"text",
"Cancel undocking");
136 undocking_->assignProperty(undocking_button_,
"toolTip", cancel_undock_msg);
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()));
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_);
150 canceled_docking_->addTransition(canceled_docking_, SIGNAL(entered()), idle_);
151 canceled_undocking_->addTransition(canceled_undocking_, SIGNAL(entered()), idle_);
156 ROSActionQTransition * idleDockTransition =
new ROSActionQTransition(QActionState::INACTIVE);
157 idleDockTransition->setTargetState(docking_);
158 idle_->addTransition(idleDockTransition);
160 ROSActionQTransition * idleUndockTransition =
new ROSActionQTransition(QActionState::INACTIVE);
161 idleUndockTransition->setTargetState(undocking_);
162 idle_->addTransition(idleUndockTransition);
164 ROSActionQTransition * dockingTransition =
new ROSActionQTransition(QActionState::ACTIVE);
165 dockingTransition->setTargetState(idle_);
166 docking_->addTransition(dockingTransition);
168 ROSActionQTransition * undockingTransition =
new ROSActionQTransition(QActionState::ACTIVE);
169 undockingTransition->setTargetState(idle_);
170 undocking_->addTransition(undockingTransition);
172 client_node_ = std::make_shared<rclcpp::Node>(
"nav2_rviz_docking_panel_node");
173 executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
174 executor_->add_node(client_node_);
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_);
183 state_machine_.setInitialState(pre_initial_);
186 QObject::connect(&state_machine_, SIGNAL(started()),
this, SLOT(startThread()));
187 state_machine_.start();
190 info_layout_->addWidget(docking_goal_status_indicator_);
191 info_layout_->addWidget(docking_result_indicator_);
192 feedback_layout_->addWidget(docking_feedback_indicator_);
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");
198 nav_stage_label->setFixedWidth(150);
199 dock_id_label->setFixedWidth(150);
200 dock_type_label->setFixedWidth(170);
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(
"}"));
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);
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_);
232 setLayout(main_layout_);
233 action_timer_.start(200,
this);
235 dock_client_ = rclcpp_action::create_client<Dock>(client_node_,
"dock_robot");
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);
241 QSignalTransition * activeDockSignal =
new QSignalTransition(
242 initial_thread_, &InitialDockThread::dockingActive);
243 activeDockSignal->setTargetState(idle_);
244 pre_initial_->addTransition(activeDockSignal);
246 QSignalTransition * activeUndockSignal =
new QSignalTransition(
247 initial_thread_, &InitialDockThread::undockingActive);
248 activeUndockSignal->setTargetState(idle_);
249 pre_initial_->addTransition(activeUndockSignal);
252 initial_thread_, &InitialDockThread::dockingActive,
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;
264 #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 2)
266 use_dock_id_checkbox_, &QCheckBox::checkStateChanged,
this, &DockingPanel::dockIdCheckbox);
269 use_dock_id_checkbox_, &QCheckBox::stateChanged,
this, &DockingPanel::dockIdCheckbox);
273 void DockingPanel::onInitialize()
275 node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
276 if (node_ptr_ ==
nullptr) {
279 rclcpp::get_logger(
"docking_panel"),
280 "Underlying ROS node no longer exists, initialization failed");
283 rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node();
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));
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));
301 if (msg->status_list.back().status == action_msgs::msg::GoalStatus::STATUS_SUCCEEDED) {
302 docking_feedback_indicator_->setText(getDockFeedbackLabel());
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));
315 void DockingPanel::startThread()
318 initial_thread_->start();
321 DockingPanel::~DockingPanel()
325 void DockingPanel::load(
const rviz_common::Config & config)
330 void DockingPanel::save(rviz_common::Config config)
const
335 void DockingPanel::onDockingButtonPressed()
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.");
344 QComboBox * combo_box = dock_type_;
346 if (combo_box->findText(
"Default") != -1) {
347 combo_box->removeItem(0);
351 if (combo_box->count() == 0) {
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();
360 if (dock_id_->text().isEmpty()) {
361 RCLCPP_ERROR(client_node_->get_logger(),
"Dock id is empty.");
364 goal_msg.dock_id = dock_id_->text().toStdString();
367 client_node_->get_logger(),
"DockRobot will be called using dock id: %s",
368 goal_msg.dock_id.c_str());
371 if (dock_pose_x_->text().isEmpty() || dock_pose_y_->text().isEmpty() ||
372 dock_pose_yaw_->text().isEmpty())
374 RCLCPP_ERROR(client_node_->get_logger(),
"Dock pose is empty.");
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();
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());
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(
"");
398 docking_result_indicator_->setText(
399 QString(std::string(
"(" + dockErrorToString(result.result->error_code) +
")").c_str()));
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)
407 RCLCPP_ERROR(client_node_->get_logger(),
"Send goal call failed");
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");
418 action_timer_.start(200,
this);
421 void DockingPanel::onUndockingButtonPressed()
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.");
430 QComboBox * combo_box = dock_type_;
432 if (combo_box->findText(
"Default") != -1) {
433 combo_box->removeItem(0);
437 if (combo_box->count() == 0) {
442 auto goal_msg = Undock::Goal();
443 goal_msg.dock_type = combo_box->currentText().toStdString();
446 client_node_->get_logger(),
"UndockRobot will be called using dock type: %s",
447 goal_msg.dock_type.c_str());
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(
"");
456 docking_result_indicator_->setText(
457 QString(std::string(
"(" + dockErrorToString(result.result->error_code) +
")").c_str()));
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)
465 RCLCPP_ERROR(client_node_->get_logger(),
"Send goal call failed");
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");
476 action_timer_.start(200,
this);
479 void DockingPanel::dockIdCheckbox()
481 if (use_dock_id_checkbox_->isChecked()) {
483 dock_id_->setEnabled(
true);
484 dock_pose_x_->setEnabled(
false);
485 dock_pose_y_->setEnabled(
false);
486 dock_pose_yaw_->setEnabled(
false);
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);
496 void DockingPanel::onCancelDocking()
498 if (dock_goal_handle_) {
499 auto future_cancel = dock_client_->async_cancel_goal(dock_goal_handle_);
501 if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
502 rclcpp::FutureReturnCode::SUCCESS)
504 RCLCPP_ERROR(client_node_->get_logger(),
"Failed to cancel goal");
506 dock_goal_handle_.reset();
510 action_timer_.stop();
513 void DockingPanel::onCancelUndocking()
515 if (undock_goal_handle_) {
516 auto future_cancel = undock_client_->async_cancel_goal(undock_goal_handle_);
518 if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
519 rclcpp::FutureReturnCode::SUCCESS)
521 RCLCPP_ERROR(client_node_->get_logger(),
"Failed to cancel goal");
523 undock_goal_handle_.reset();
527 action_timer_.stop();
530 void DockingPanel::timerEvent(QTimerEvent * event)
532 if (event->timerId() == action_timer_.timerId()) {
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));
541 executor_->spin_some();
542 auto status = dock_goal_handle_->get_status();
545 if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
546 status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
548 state_machine_.postEvent(
new ROSActionQEvent(QActionState::ACTIVE));
550 state_machine_.postEvent(
new ROSActionQEvent(QActionState::INACTIVE));
551 action_timer_.stop();
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));
560 executor_->spin_some();
561 auto status = undock_goal_handle_->get_status();
564 if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
565 status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
567 state_machine_.postEvent(
new ROSActionQEvent(QActionState::ACTIVE));
569 state_machine_.postEvent(
new ROSActionQEvent(QActionState::INACTIVE));
570 action_timer_.stop();
576 inline QString DockingPanel::getDockFeedbackLabel(Dock::Feedback msg)
578 return QString(std::string(
"<table>" + toLabel(msg) +
"</table>").c_str());
582 inline std::string DockingPanel::toLabel(T & msg)
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) +
594 inline std::string DockingPanel::toString(
double val,
int precision)
596 std::ostringstream out;
597 out.precision(precision);
598 out << std::fixed << val;
602 inline std::string DockingPanel::dockStateToString(int16_t state)
608 return "nav. to staging pose";
610 return "initial perception";
612 return "controlling";
614 return "wait for charge";
622 inline std::string DockingPanel::dockErrorToString(int16_t error_code)
624 switch (error_code) {
628 return "dock not in database";
630 return "dock not valid";
632 return "failed to stage";
634 return "failed to detect dock";
636 return "failed to control";
638 return "failed to charge";
647 #include <pluginlib/class_list_macros.hpp>
Panel to interface to the docking server.