17 #include <QVBoxLayout>
18 #include <QHBoxLayout>
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>
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"
41 using namespace std::chrono_literals;
43 namespace nav2_rviz_plugins
46 DockingPanel::DockingPanel(QWidget * parent)
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;
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";
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);
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);
89 pre_initial_->assignProperty(undocking_button_,
"text",
"Undock robot");
90 pre_initial_->assignProperty(undocking_button_,
"enabled",
false);
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);
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);
109 idle_->assignProperty(undocking_button_,
"text",
"Undock robot");
110 idle_->assignProperty(undocking_button_,
"toolTip", undock_msg);
111 idle_->assignProperty(undocking_button_,
"enabled",
true);
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);
121 canceled_docking_ =
new QState();
122 canceled_docking_->setObjectName(
"canceled_docking");
125 canceled_undocking_ =
new QState();
126 canceled_undocking_->setObjectName(
"canceled_undocking");
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);
134 docking_->assignProperty(undocking_button_,
"enabled",
false);
137 undocking_ =
new QState();
138 undocking_->setObjectName(
"undocking");
139 undocking_->assignProperty(docking_button_,
"enabled",
false);
141 undocking_->assignProperty(undocking_button_,
"text",
"Cancel undocking");
142 undocking_->assignProperty(undocking_button_,
"toolTip", cancel_undock_msg);
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()));
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_);
156 canceled_docking_->addTransition(canceled_docking_, SIGNAL(entered()), idle_);
157 canceled_undocking_->addTransition(canceled_undocking_, SIGNAL(entered()), idle_);
162 ROSActionQTransition * idleDockTransition =
new ROSActionQTransition(QActionState::INACTIVE);
163 idleDockTransition->setTargetState(docking_);
164 idle_->addTransition(idleDockTransition);
166 ROSActionQTransition * idleUndockTransition =
new ROSActionQTransition(QActionState::INACTIVE);
167 idleUndockTransition->setTargetState(undocking_);
168 idle_->addTransition(idleUndockTransition);
170 ROSActionQTransition * dockingTransition =
new ROSActionQTransition(QActionState::ACTIVE);
171 dockingTransition->setTargetState(idle_);
172 docking_->addTransition(dockingTransition);
174 ROSActionQTransition * undockingTransition =
new ROSActionQTransition(QActionState::ACTIVE);
175 undockingTransition->setTargetState(idle_);
176 undocking_->addTransition(undockingTransition);
178 client_node_ = std::make_shared<rclcpp::Node>(
"nav2_rviz_docking_panel_node");
179 executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
180 executor_->add_node(client_node_);
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_);
189 state_machine_.setInitialState(pre_initial_);
192 QObject::connect(&state_machine_, SIGNAL(started()),
this, SLOT(startThread()));
193 state_machine_.start();
196 info_layout_->addWidget(docking_goal_status_indicator_);
197 info_layout_->addWidget(docking_result_indicator_);
198 feedback_layout_->addWidget(docking_feedback_indicator_);
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");
204 nav_stage_label->setFixedWidth(150);
205 dock_id_label->setFixedWidth(150);
206 dock_type_label->setFixedWidth(170);
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(
"}"));
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);
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_);
238 setLayout(main_layout_);
239 action_timer_.start(200,
this);
241 dock_client_ = rclcpp_action::create_client<Dock>(client_node_,
"dock_robot");
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);
247 QSignalTransition * activeDockSignal =
new QSignalTransition(
248 initial_thread_, &InitialDockThread::dockingActive);
249 activeDockSignal->setTargetState(idle_);
250 pre_initial_->addTransition(activeDockSignal);
252 QSignalTransition * activeUndockSignal =
new QSignalTransition(
253 initial_thread_, &InitialDockThread::undockingActive);
254 activeUndockSignal->setTargetState(idle_);
255 pre_initial_->addTransition(activeUndockSignal);
258 initial_thread_, &InitialDockThread::dockingActive,
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;
270 #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 2)
272 use_dock_id_checkbox_, &QCheckBox::checkStateChanged,
this, &DockingPanel::dockIdCheckbox);
275 use_dock_id_checkbox_, &QCheckBox::stateChanged,
this, &DockingPanel::dockIdCheckbox);
279 void DockingPanel::onInitialize()
281 node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
282 if (node_ptr_ ==
nullptr) {
285 rclcpp::get_logger(
"docking_panel"),
286 "Underlying ROS node no longer exists, initialization failed");
289 rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node();
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));
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));
307 if (msg->status_list.back().status == action_msgs::msg::GoalStatus::STATUS_SUCCEEDED) {
308 docking_feedback_indicator_->setText(getDockFeedbackLabel());
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));
321 void DockingPanel::startThread()
324 initial_thread_->start();
327 DockingPanel::~DockingPanel()
331 void DockingPanel::load(
const rviz_common::Config & config)
336 void DockingPanel::save(rviz_common::Config config)
const
341 void DockingPanel::onDockingButtonPressed()
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.");
350 QComboBox * combo_box = dock_type_;
352 if (combo_box->findText(
"Default") != -1) {
353 combo_box->removeItem(0);
357 if (combo_box->count() == 0) {
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();
366 if (dock_id_->text().isEmpty()) {
367 RCLCPP_ERROR(client_node_->get_logger(),
"Dock id is empty.");
370 goal_msg.dock_id = dock_id_->text().toStdString();
373 client_node_->get_logger(),
"DockRobot will be called using dock id: %s",
374 goal_msg.dock_id.c_str());
377 if (dock_pose_x_->text().isEmpty() || dock_pose_y_->text().isEmpty() ||
378 dock_pose_yaw_->text().isEmpty())
380 RCLCPP_ERROR(client_node_->get_logger(),
"Dock pose is empty.");
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();
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());
398 auto send_goal_options = nav2::ActionClient<Dock>::SendGoalOptions();
399 send_goal_options.result_callback = [
this](
const DockGoalHandle::WrappedResult & result) {
402 if (dock_goal_handle_ && dock_goal_handle_->get_goal_id() == result.goal_id) {
403 dock_goal_handle_.reset();
405 if (result.result->success) {
406 docking_result_indicator_->setText(
"");
408 docking_result_indicator_->setText(
409 QString(std::string(
"(" + dockErrorToString(result.result->error_code) +
")").c_str()));
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)
417 RCLCPP_ERROR(client_node_->get_logger(),
"Send goal call failed");
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");
428 action_timer_.start(200,
this);
431 void DockingPanel::onUndockingButtonPressed()
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.");
440 QComboBox * combo_box = dock_type_;
442 if (combo_box->findText(
"Default") != -1) {
443 combo_box->removeItem(0);
447 if (combo_box->count() == 0) {
452 auto goal_msg = Undock::Goal();
453 goal_msg.dock_type = combo_box->currentText().toStdString();
456 client_node_->get_logger(),
"UndockRobot will be called using dock type: %s",
457 goal_msg.dock_type.c_str());
460 auto send_goal_options = nav2::ActionClient<Undock>::SendGoalOptions();
461 send_goal_options.result_callback = [
this](
const UndockGoalHandle::WrappedResult & result) {
464 if (undock_goal_handle_ && undock_goal_handle_->get_goal_id() == result.goal_id) {
465 undock_goal_handle_.reset();
467 if (result.result->success) {
468 docking_result_indicator_->setText(
"");
470 docking_result_indicator_->setText(
471 QString(std::string(
"(" + dockErrorToString(result.result->error_code) +
")").c_str()));
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)
479 RCLCPP_ERROR(client_node_->get_logger(),
"Send goal call failed");
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");
490 action_timer_.start(200,
this);
493 void DockingPanel::dockIdCheckbox()
495 if (use_dock_id_checkbox_->isChecked()) {
497 dock_id_->setEnabled(
true);
498 dock_pose_x_->setEnabled(
false);
499 dock_pose_y_->setEnabled(
false);
500 dock_pose_yaw_->setEnabled(
false);
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);
510 void DockingPanel::onCancelDocking()
512 if (dock_goal_handle_) {
513 auto future_cancel = dock_client_->async_cancel_goal(dock_goal_handle_);
515 if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
516 rclcpp::FutureReturnCode::SUCCESS)
518 RCLCPP_ERROR(client_node_->get_logger(),
"Failed to cancel goal");
520 dock_goal_handle_.reset();
524 action_timer_.stop();
527 void DockingPanel::onCancelUndocking()
529 if (undock_goal_handle_) {
530 auto future_cancel = undock_client_->async_cancel_goal(undock_goal_handle_);
532 if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
533 rclcpp::FutureReturnCode::SUCCESS)
535 RCLCPP_ERROR(client_node_->get_logger(),
"Failed to cancel goal");
537 undock_goal_handle_.reset();
541 action_timer_.stop();
544 void DockingPanel::timerEvent(QTimerEvent * event)
546 if (event->timerId() == action_timer_.timerId()) {
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));
556 auto goal_handle = dock_goal_handle_;
557 executor_->spin_some();
558 auto status = goal_handle->get_status();
561 if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
562 status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
564 state_machine_.postEvent(
new ROSActionQEvent(QActionState::ACTIVE));
566 state_machine_.postEvent(
new ROSActionQEvent(QActionState::INACTIVE));
567 action_timer_.stop();
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));
577 auto goal_handle = undock_goal_handle_;
578 executor_->spin_some();
579 auto status = goal_handle->get_status();
582 if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
583 status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
585 state_machine_.postEvent(
new ROSActionQEvent(QActionState::ACTIVE));
587 state_machine_.postEvent(
new ROSActionQEvent(QActionState::INACTIVE));
588 action_timer_.stop();
594 inline QString DockingPanel::getDockFeedbackLabel(Dock::Feedback msg)
596 return QString(std::string(
"<table>" + toLabel(msg) +
"</table>").c_str());
600 inline std::string DockingPanel::toLabel(T & msg)
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) +
612 inline std::string DockingPanel::toString(
double val,
int precision)
614 std::ostringstream out;
615 out.precision(precision);
616 out << std::fixed << val;
620 inline std::string DockingPanel::dockStateToString(int16_t state)
626 return "nav. to staging pose";
628 return "initial perception";
630 return "controlling";
632 return "wait for charge";
640 inline std::string DockingPanel::dockErrorToString(int16_t error_code)
642 switch (error_code) {
646 return "dock not in database";
648 return "dock not valid";
650 return "failed to stage";
652 return "failed to detect dock";
654 return "failed to control";
656 return "failed to charge";
665 #include <pluginlib/class_list_macros.hpp>
Panel to interface to the docking server.