Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
nav2_panel.cpp
1 // Copyright (c) 2019 Intel Corporation
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 #include "nav2_rviz_plugins/nav2_panel.hpp"
16 
17 #include <QtConcurrent/QtConcurrent>
18 #include <QVBoxLayout>
19 #include <QHBoxLayout>
20 #include <QTextEdit>
21 #include <QCheckBox>
22 
23 #include <ctype.h>
24 #include <memory>
25 #include <vector>
26 #include <utility>
27 #include <chrono>
28 #include <string>
29 
30 #include "nav2_rviz_plugins/goal_common.hpp"
31 #include "nav2_rviz_plugins/utils.hpp"
32 #include "rviz_common/display_context.hpp"
33 #include "rviz_common/load_resource.hpp"
34 #include "ament_index_cpp/get_package_share_directory.hpp"
35 #include "yaml-cpp/yaml.h"
36 #include "geometry_msgs/msg/pose2_d.hpp"
37 
38 using namespace std::chrono_literals;
39 
40 namespace nav2_rviz_plugins
41 {
42 using nav2_util::geometry_utils::orientationAroundZAxis;
43 
44 // Define global GoalPoseUpdater so that the nav2 GoalTool plugin can access to update goal pose
45 GoalPoseUpdater GoalUpdater;
46 
47 Nav2Panel::Nav2Panel(QWidget * parent)
48 : Panel(parent),
49  server_timeout_(100)
50 {
51  // Create the control button and its tooltip
52 
53  start_reset_button_ = new QPushButton;
54  pause_resume_button_ = new QPushButton;
55  navigation_mode_button_ = new QPushButton;
56  save_waypoints_button_ = new QPushButton;
57  load_waypoints_button_ = new QPushButton;
58  pause_waypoint_button_ = new QPushButton;
59  navigation_status_indicator_ = new QLabel;
60  localization_status_indicator_ = new QLabel;
61  navigation_goal_status_indicator_ = new QLabel;
62  navigation_feedback_indicator_ = new QLabel;
63  waypoint_status_indicator_ = new QLabel;
64  number_of_loops_ = new QLabel;
65  nr_of_loops_ = new QLineEdit;
66  store_initial_pose_checkbox_ = new QCheckBox("Store initial_pose");
67 
68  // Create the state machine used to present the proper control button states in the UI
69 
70  const char * startup_msg = "Configure and activate all nav2 lifecycle nodes";
71  const char * shutdown_msg = "Deactivate and cleanup all nav2 lifecycle nodes";
72  const char * cancel_msg = "Cancel navigation";
73  const char * pause_msg = "Deactivate all nav2 lifecycle nodes";
74  const char * resume_msg = "Activate all nav2 lifecycle nodes";
75  const char * single_goal_msg = "Change to waypoint / nav through poses style navigation";
76  const char * waypoint_goal_msg = "Start following waypoints";
77  const char * nft_goal_msg = "Start navigating through poses";
78  const char * cancel_waypoint_msg = "Cancel waypoint / viapoint accumulation mode";
79 
80  const QString navigation_active("<table><tr><td width=150><b>Navigation:</b></td>"
81  "<td><font color=green>active</color></td></tr></table>");
82  const QString navigation_inactive("<table><tr><td width=150><b>Navigation:</b></td>"
83  "<td>inactive</td></tr></table>");
84  const QString navigation_unknown("<table><tr><td width=150><b>Navigation:</b></td>"
85  "<td>unknown</td></tr></table>");
86  const QString localization_active("<table><tr><td width=150><b>Localization:</b></td>"
87  "<td><font color=green>active</color></td></tr></table>");
88  const QString localization_inactive("<table><tr><td width=150><b>Localization:</b></td>"
89  "<td>inactive</td></tr></table>");
90  const QString localization_unknown("<table><tr><td width=150><b>Localization:</b></td>"
91  "<td>unknown</td></tr></table>");
92 
93  navigation_status_indicator_->setText(navigation_unknown);
94  localization_status_indicator_->setText(localization_unknown);
95  navigation_goal_status_indicator_->setText(nav2_rviz_plugins::getGoalStatusLabel());
96  number_of_loops_->setText("Num of loops");
97  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
98  navigation_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
99  localization_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
100  navigation_goal_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
101  navigation_feedback_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
102  waypoint_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
103 
104  pre_initial_ = new QState();
105  pre_initial_->setObjectName("pre_initial");
106  pre_initial_->assignProperty(start_reset_button_, "text", "Startup");
107  pre_initial_->assignProperty(start_reset_button_, "enabled", false);
108 
109  pre_initial_->assignProperty(pause_resume_button_, "text", "Pause");
110  pre_initial_->assignProperty(pause_resume_button_, "enabled", false);
111 
112  pre_initial_->assignProperty(save_waypoints_button_, "text", "Save WPs");
113  pre_initial_->assignProperty(save_waypoints_button_, "enabled", false);
114 
115  pre_initial_->assignProperty(load_waypoints_button_, "text", "Load WPs");
116  pre_initial_->assignProperty(load_waypoints_button_, "enabled", false);
117 
118  pre_initial_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
119  pre_initial_->assignProperty(pause_waypoint_button_, "enabled", false);
120 
121  pre_initial_->assignProperty(nr_of_loops_, "text", "0");
122  pre_initial_->assignProperty(nr_of_loops_, "enabled", false);
123 
124  pre_initial_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
125 
126  pre_initial_->assignProperty(
127  navigation_mode_button_, "text",
128  "Waypoint / Nav Through Poses Mode");
129  pre_initial_->assignProperty(navigation_mode_button_, "enabled", false);
130 
131  initial_ = new QState();
132  initial_->setObjectName("initial");
133  initial_->assignProperty(start_reset_button_, "text", "Startup");
134  initial_->assignProperty(start_reset_button_, "toolTip", startup_msg);
135  initial_->assignProperty(start_reset_button_, "enabled", true);
136 
137  initial_->assignProperty(pause_resume_button_, "text", "Pause");
138  initial_->assignProperty(pause_resume_button_, "enabled", false);
139 
140  initial_->assignProperty(navigation_mode_button_, "text", "Waypoint / Nav Through Poses Mode");
141  initial_->assignProperty(navigation_mode_button_, "enabled", false);
142 
143  initial_->assignProperty(save_waypoints_button_, "text", "Save WPs");
144  initial_->assignProperty(save_waypoints_button_, "enabled", false);
145 
146  initial_->assignProperty(load_waypoints_button_, "text", "Load WPs");
147  initial_->assignProperty(load_waypoints_button_, "enabled", false);
148 
149  initial_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
150  initial_->assignProperty(pause_waypoint_button_, "enabled", false);
151 
152  initial_->assignProperty(nr_of_loops_, "text", "0");
153  initial_->assignProperty(nr_of_loops_, "enabled", false);
154 
155  initial_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
156 
157  // State entered when navigate_to_pose action is not active
158  idle_ = new QState();
159  idle_->setObjectName("idle");
160  idle_->assignProperty(start_reset_button_, "text", "Reset");
161  idle_->assignProperty(start_reset_button_, "toolTip", shutdown_msg);
162  idle_->assignProperty(start_reset_button_, "enabled", true);
163 
164  idle_->assignProperty(pause_resume_button_, "text", "Pause");
165  idle_->assignProperty(pause_resume_button_, "enabled", true);
166  idle_->assignProperty(pause_resume_button_, "toolTip", pause_msg);
167 
168  idle_->assignProperty(navigation_mode_button_, "text", "Waypoint / Nav Through Poses Mode");
169  idle_->assignProperty(navigation_mode_button_, "enabled", true);
170  idle_->assignProperty(navigation_mode_button_, "toolTip", single_goal_msg);
171 
172  idle_->assignProperty(save_waypoints_button_, "text", "Save WPs");
173  idle_->assignProperty(save_waypoints_button_, "enabled", false);
174 
175  idle_->assignProperty(load_waypoints_button_, "text", "Load WPs");
176  idle_->assignProperty(load_waypoints_button_, "enabled", false);
177 
178  idle_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
179  idle_->assignProperty(pause_waypoint_button_, "enabled", false);
180 
181  idle_->assignProperty(nr_of_loops_, "text", "0");
182  idle_->assignProperty(nr_of_loops_, "enabled", false);
183 
184  idle_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
185 
186  // State entered when navigate_to_pose action is not active
187  accumulating_ = new QState();
188  accumulating_->setObjectName("accumulating");
189  accumulating_->assignProperty(start_reset_button_, "text", "Cancel Accumulation");
190  accumulating_->assignProperty(start_reset_button_, "toolTip", cancel_waypoint_msg);
191  accumulating_->assignProperty(start_reset_button_, "enabled", true);
192 
193  accumulating_->assignProperty(pause_resume_button_, "text", "Start Nav Through Poses");
194  accumulating_->assignProperty(pause_resume_button_, "enabled", true);
195  accumulating_->assignProperty(pause_resume_button_, "toolTip", nft_goal_msg);
196 
197  accumulating_->assignProperty(navigation_mode_button_, "text", "Start Waypoint Following");
198  accumulating_->assignProperty(navigation_mode_button_, "enabled", true);
199  accumulating_->assignProperty(navigation_mode_button_, "toolTip", waypoint_goal_msg);
200 
201  accumulating_->assignProperty(save_waypoints_button_, "text", "Save WPs");
202  accumulating_->assignProperty(save_waypoints_button_, "enabled", true);
203 
204  accumulating_->assignProperty(load_waypoints_button_, "text", "Load WPs");
205  accumulating_->assignProperty(load_waypoints_button_, "enabled", true);
206 
207  accumulating_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
208  accumulating_->assignProperty(pause_waypoint_button_, "enabled", false);
209 
210  accumulating_->assignProperty(nr_of_loops_, "text", QString::fromStdString(loop_no_));
211  accumulating_->assignProperty(nr_of_loops_, "enabled", true);
212  accumulating_->assignProperty(store_initial_pose_checkbox_, "enabled", true);
213 
214  accumulated_wp_ = new QState();
215  accumulated_wp_->setObjectName("accumulated_wp");
216  accumulated_wp_->assignProperty(start_reset_button_, "text", "Cancel");
217  accumulated_wp_->assignProperty(start_reset_button_, "toolTip", cancel_msg);
218  accumulated_wp_->assignProperty(start_reset_button_, "enabled", true);
219 
220  accumulated_wp_->assignProperty(pause_resume_button_, "text", "Start Nav Through Poses");
221  accumulated_wp_->assignProperty(pause_resume_button_, "enabled", false);
222  accumulated_wp_->assignProperty(pause_resume_button_, "toolTip", nft_goal_msg);
223 
224  accumulated_wp_->assignProperty(navigation_mode_button_, "text", "Start Waypoint Following");
225  accumulated_wp_->assignProperty(navigation_mode_button_, "enabled", false);
226  accumulated_wp_->assignProperty(navigation_mode_button_, "toolTip", waypoint_goal_msg);
227 
228  accumulated_wp_->assignProperty(save_waypoints_button_, "text", "Save WPs");
229  accumulated_wp_->assignProperty(save_waypoints_button_, "enabled", false);
230 
231  accumulated_wp_->assignProperty(load_waypoints_button_, "text", "Load WPs");
232  accumulated_wp_->assignProperty(load_waypoints_button_, "enabled", false);
233 
234  accumulated_wp_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
235  accumulated_wp_->assignProperty(pause_waypoint_button_, "enabled", true);
236 
237  accumulated_wp_->assignProperty(nr_of_loops_, "enabled", false);
238  accumulated_wp_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
239 
240  accumulated_nav_through_poses_ = new QState();
241  accumulated_nav_through_poses_->setObjectName("accumulated_nav_through_poses");
242  accumulated_nav_through_poses_->assignProperty(start_reset_button_, "text", "Cancel");
243  accumulated_nav_through_poses_->assignProperty(start_reset_button_, "toolTip", cancel_msg);
244 
245  accumulated_nav_through_poses_->assignProperty(save_waypoints_button_, "text", "Save WPs");
246  accumulated_nav_through_poses_->assignProperty(save_waypoints_button_, "enabled", false);
247 
248  accumulated_nav_through_poses_->assignProperty(load_waypoints_button_, "text", "Load WPs");
249  accumulated_nav_through_poses_->assignProperty(load_waypoints_button_, "enabled", false);
250 
251  accumulated_nav_through_poses_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
252  accumulated_nav_through_poses_->assignProperty(pause_waypoint_button_, "enabled", false);
253 
254  accumulated_nav_through_poses_->assignProperty(nr_of_loops_, "enabled", false);
255 
256  accumulated_nav_through_poses_->assignProperty(start_reset_button_, "enabled", true);
257  accumulated_nav_through_poses_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
258 
259  accumulated_nav_through_poses_->assignProperty(
260  pause_resume_button_, "text",
261  "Start Nav Through Poses");
262  accumulated_nav_through_poses_->assignProperty(pause_resume_button_, "enabled", false);
263  accumulated_nav_through_poses_->assignProperty(pause_resume_button_, "toolTip", nft_goal_msg);
264 
265  accumulated_nav_through_poses_->assignProperty(
266  navigation_mode_button_, "text",
267  "Start Waypoint Following");
268  accumulated_nav_through_poses_->assignProperty(navigation_mode_button_, "enabled", false);
269  accumulated_nav_through_poses_->assignProperty(
270  navigation_mode_button_, "toolTip",
271  waypoint_goal_msg);
272 
273  accumulated_nav_through_poses_->assignProperty(
274  nr_of_loops_, "text",
275  QString::fromStdString(loop_no_));
276  accumulated_nav_through_poses_->assignProperty(nr_of_loops_, "enabled", false);
277  accumulated_nav_through_poses_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
278 
279  // State entered to cancel the navigate_to_pose action
280  canceled_ = new QState();
281  canceled_->setObjectName("canceled");
282 
283  // State entered to reset the nav2 lifecycle nodes
284  reset_ = new QState();
285  reset_->setObjectName("reset");
286  // State entered while the navigate_to_pose action is active
287  running_ = new QState();
288  running_->setObjectName("running");
289  running_->assignProperty(start_reset_button_, "text", "Cancel");
290  running_->assignProperty(start_reset_button_, "toolTip", cancel_msg);
291 
292  running_->assignProperty(pause_resume_button_, "text", "Pause");
293  running_->assignProperty(pause_resume_button_, "enabled", false);
294 
295  running_->assignProperty(navigation_mode_button_, "text", "Waypoint mode");
296  running_->assignProperty(navigation_mode_button_, "enabled", false);
297 
298  running_->assignProperty(save_waypoints_button_, "text", "Save WPs");
299  running_->assignProperty(save_waypoints_button_, "enabled", false);
300 
301  running_->assignProperty(load_waypoints_button_, "text", "Load WPs");
302  running_->assignProperty(load_waypoints_button_, "enabled", false);
303 
304  running_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
305  running_->assignProperty(pause_waypoint_button_, "enabled", false);
306 
307  running_->assignProperty(nr_of_loops_, "text", "0");
308  running_->assignProperty(nr_of_loops_, "enabled", false);
309 
310  running_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
311 
312  // State entered when pause is requested
313  paused_ = new QState();
314  paused_->setObjectName("pausing");
315  paused_->assignProperty(start_reset_button_, "text", "Reset");
316  paused_->assignProperty(start_reset_button_, "toolTip", shutdown_msg);
317 
318  paused_->assignProperty(pause_resume_button_, "text", "Resume");
319  paused_->assignProperty(pause_resume_button_, "toolTip", resume_msg);
320  paused_->assignProperty(pause_resume_button_, "enabled", true);
321 
322  paused_->assignProperty(navigation_mode_button_, "text", "");
323  paused_->assignProperty(navigation_mode_button_, "enabled", false);
324 
325  paused_->assignProperty(save_waypoints_button_, "text", "Save WPs");
326  paused_->assignProperty(save_waypoints_button_, "enabled", false);
327 
328  paused_->assignProperty(load_waypoints_button_, "text", "Load WPs");
329  paused_->assignProperty(load_waypoints_button_, "enabled", false);
330 
331  paused_->assignProperty(pause_waypoint_button_, "text", "Pause WP");
332  paused_->assignProperty(pause_waypoint_button_, "enabled", false);
333 
334  paused_->assignProperty(nr_of_loops_, "enabled", false);
335 
336  paused_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
337 
338  // State entered to resume the nav2 lifecycle nodes
339  resumed_ = new QState();
340  resumed_->setObjectName("resuming");
341 
342  // States entered to pause and Resume WPs
343  resumed_wp_ = new QState();
344  resumed_wp_->setObjectName("running");
345  resumed_wp_->assignProperty(start_reset_button_, "text", "Cancel");
346  resumed_wp_->assignProperty(start_reset_button_, "toolTip", cancel_msg);
347 
348  resumed_wp_->assignProperty(pause_resume_button_, "text", "Start Nav Through Poses");
349  resumed_wp_->assignProperty(pause_resume_button_, "enabled", false);
350 
351  resumed_wp_->assignProperty(navigation_mode_button_, "text", "Start Waypoint Following");
352  resumed_wp_->assignProperty(navigation_mode_button_, "enabled", false);
353 
354  resumed_wp_->assignProperty(save_waypoints_button_, "text", "Save WPs");
355  resumed_wp_->assignProperty(save_waypoints_button_, "enabled", true);
356 
357  resumed_wp_->assignProperty(load_waypoints_button_, "text", "Load WPs");
358  resumed_wp_->assignProperty(load_waypoints_button_, "enabled", false);
359 
360  resumed_wp_->assignProperty(pause_waypoint_button_, "text", "Resume WP");
361  resumed_wp_->assignProperty(pause_waypoint_button_, "enabled", true);
362 
363  resumed_wp_->assignProperty(nr_of_loops_, "enabled", false);
364  resumed_wp_->assignProperty(store_initial_pose_checkbox_, "enabled", false);
365 
366  QObject::connect(initial_, SIGNAL(exited()), this, SLOT(onStartup()));
367  QObject::connect(canceled_, SIGNAL(exited()), this, SLOT(onCancel()));
368  QObject::connect(reset_, SIGNAL(exited()), this, SLOT(onShutdown()));
369  QObject::connect(paused_, SIGNAL(entered()), this, SLOT(onPause()));
370  QObject::connect(resumed_, SIGNAL(exited()), this, SLOT(onResume()));
371  QObject::connect(accumulating_, SIGNAL(entered()), this, SLOT(onAccumulating()));
372  QObject::connect(accumulated_wp_, SIGNAL(entered()), this, SLOT(onAccumulatedWp()));
373  QObject::connect(resumed_wp_, SIGNAL(entered()), this, SLOT(onResumedWp()));
374  QObject::connect(
375  accumulated_nav_through_poses_, SIGNAL(entered()), this,
376  SLOT(onAccumulatedNTP()));
377  QObject::connect(
378  save_waypoints_button_,
379  &QPushButton::released,
380  this, &Nav2Panel::handleGoalSaver);
381  QObject::connect(
382  load_waypoints_button_,
383  &QPushButton::released,
384  this,
385  &Nav2Panel::handleGoalLoader);
386  QObject::connect(
387  nr_of_loops_,
388  &QLineEdit::editingFinished,
389  this,
390  &Nav2Panel::loophandler);
391  QObject::connect(
392  store_initial_pose_checkbox_,
393  &QCheckBox::stateChanged,
394  this,
395  &Nav2Panel::initialStateHandler);
396 
397  // Start/Reset button click transitions
398  initial_->addTransition(start_reset_button_, SIGNAL(clicked()), idle_);
399  idle_->addTransition(start_reset_button_, SIGNAL(clicked()), reset_);
400  running_->addTransition(start_reset_button_, SIGNAL(clicked()), canceled_);
401  paused_->addTransition(start_reset_button_, SIGNAL(clicked()), reset_);
402  idle_->addTransition(navigation_mode_button_, SIGNAL(clicked()), accumulating_);
403  accumulating_->addTransition(navigation_mode_button_, SIGNAL(clicked()), accumulated_wp_);
404  accumulating_->addTransition(
405  pause_resume_button_, SIGNAL(
406  clicked()), accumulated_nav_through_poses_);
407  accumulating_->addTransition(start_reset_button_, SIGNAL(clicked()), idle_);
408  accumulated_wp_->addTransition(start_reset_button_, SIGNAL(clicked()), canceled_);
409  accumulated_nav_through_poses_->addTransition(start_reset_button_, SIGNAL(clicked()), canceled_);
410 
411  // Internal state transitions
412  canceled_->addTransition(canceled_, SIGNAL(entered()), idle_);
413  reset_->addTransition(reset_, SIGNAL(entered()), initial_);
414  resumed_->addTransition(resumed_, SIGNAL(entered()), idle_);
415 
416  // Pause/Resume button click transitions
417  idle_->addTransition(pause_resume_button_, SIGNAL(clicked()), paused_);
418  paused_->addTransition(pause_resume_button_, SIGNAL(clicked()), resumed_);
419 
420  // Pause/Resume button waypoint transition
421  accumulated_wp_->addTransition(pause_waypoint_button_, SIGNAL(clicked()), resumed_wp_);
422  resumed_wp_->addTransition(pause_waypoint_button_, SIGNAL(clicked()), accumulated_wp_);
423  resumed_wp_->addTransition(start_reset_button_, SIGNAL(clicked()), canceled_);
424 
425  // ROSAction Transitions: So when actions are updated remotely (failing, succeeding, etc)
426  // the state of the application will also update. This means that if in the processing
427  // states and then goes inactive, move back to the idle state. Vise versa as well.
428  ROSActionQTransition * idleTransition = new ROSActionQTransition(QActionState::INACTIVE);
429  idleTransition->setTargetState(running_);
430  idle_->addTransition(idleTransition);
431 
432  ROSActionQTransition * runningTransition = new ROSActionQTransition(QActionState::ACTIVE);
433  runningTransition->setTargetState(idle_);
434  running_->addTransition(runningTransition);
435 
436  ROSActionQTransition * idleAccumulatedWpTransition =
437  new ROSActionQTransition(QActionState::INACTIVE);
438  idleAccumulatedWpTransition->setTargetState(accumulated_wp_);
439  idle_->addTransition(idleAccumulatedWpTransition);
440 
441  ROSActionQTransition * accumulatedWpTransition = new ROSActionQTransition(QActionState::ACTIVE);
442  accumulatedWpTransition->setTargetState(accumulating_);
443  accumulated_wp_->addTransition(accumulatedWpTransition);
444 
445  ROSActionQTransition * idleAccumulatedNTPTransition =
446  new ROSActionQTransition(QActionState::INACTIVE);
447  idleAccumulatedNTPTransition->setTargetState(accumulated_nav_through_poses_);
448  idle_->addTransition(idleAccumulatedNTPTransition);
449 
450  ROSActionQTransition * accumulatedNTPTransition = new ROSActionQTransition(QActionState::ACTIVE);
451  accumulatedNTPTransition->setTargetState(idle_);
452  accumulated_nav_through_poses_->addTransition(accumulatedNTPTransition);
453 
454  auto options = rclcpp::NodeOptions().arguments(
455  {"--ros-args", "--remap", "__node:=rviz_navigation_dialog_action_client", "--"});
456  client_node_ = std::make_shared<rclcpp::Node>("_", options);
457 
458  client_nav_ = std::make_shared<nav2_lifecycle_manager::LifecycleManagerClient>(
459  "lifecycle_manager_navigation", client_node_);
460  client_loc_ = std::make_shared<nav2_lifecycle_manager::LifecycleManagerClient>(
461  "lifecycle_manager_localization", client_node_);
462  initial_thread_ = new InitialThread(client_nav_, client_loc_);
463  connect(initial_thread_, &InitialThread::finished, initial_thread_, &QObject::deleteLater);
464 
465  QSignalTransition * activeSignal = new QSignalTransition(
466  initial_thread_,
467  &InitialThread::navigationActive);
468  activeSignal->setTargetState(idle_);
469  pre_initial_->addTransition(activeSignal);
470  QSignalTransition * inactiveSignal = new QSignalTransition(
471  initial_thread_,
472  &InitialThread::navigationInactive);
473  inactiveSignal->setTargetState(initial_);
474  pre_initial_->addTransition(inactiveSignal);
475 
476  QObject::connect(
477  initial_thread_, &InitialThread::navigationActive,
478  [this, navigation_active] {
479  navigation_status_indicator_->setText(navigation_active);
480  });
481  QObject::connect(
482  initial_thread_, &InitialThread::navigationInactive,
483  [this, navigation_inactive] {
484  navigation_status_indicator_->setText(navigation_inactive);
485  navigation_goal_status_indicator_->setText(nav2_rviz_plugins::getGoalStatusLabel());
486  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
487  });
488  QObject::connect(
489  initial_thread_, &InitialThread::localizationActive,
490  [this, localization_active] {
491  localization_status_indicator_->setText(localization_active);
492  });
493  QObject::connect(
494  initial_thread_, &InitialThread::localizationInactive,
495  [this, localization_inactive] {
496  localization_status_indicator_->setText(localization_inactive);
497  });
498 
499  state_machine_.addState(pre_initial_);
500  state_machine_.addState(initial_);
501  state_machine_.addState(idle_);
502  state_machine_.addState(running_);
503  state_machine_.addState(canceled_);
504  state_machine_.addState(reset_);
505  state_machine_.addState(paused_);
506  state_machine_.addState(resumed_);
507  state_machine_.addState(accumulating_);
508  state_machine_.addState(accumulated_wp_);
509  state_machine_.addState(accumulated_nav_through_poses_);
510  state_machine_.addState(resumed_wp_);
511 
512  state_machine_.setInitialState(pre_initial_);
513 
514  // delay starting initial thread until state machine has started or a race occurs
515  QObject::connect(&state_machine_, SIGNAL(started()), this, SLOT(startThread()));
516  state_machine_.start();
517 
518  // Lay out the items in the panel
519  QVBoxLayout * main_layout = new QVBoxLayout;
520  QHBoxLayout * side_layout = new QHBoxLayout;
521  QVBoxLayout * status_layout = new QVBoxLayout;
522  QHBoxLayout * logo_layout = new QHBoxLayout;
523  QVBoxLayout * group_box_layout = new QVBoxLayout;
524 
525  QGroupBox * groupBox = new QGroupBox(tr("Tools for WP-Following"));
526  imgDisplayLabel_ = new QLabel("");
527  imgDisplayLabel_->setPixmap(
528  rviz_common::loadPixmap("package://nav2_rviz_plugins/icons/classes/nav2_logo_small.png"));
529 
530  status_layout->addWidget(navigation_status_indicator_);
531  status_layout->addWidget(localization_status_indicator_);
532  status_layout->addWidget(navigation_goal_status_indicator_);
533 
534  logo_layout->addWidget(imgDisplayLabel_, 5, Qt::AlignRight);
535 
536  side_layout->addLayout(status_layout);
537  side_layout->addLayout(logo_layout);
538 
539  main_layout->addLayout(side_layout);
540  main_layout->addWidget(navigation_feedback_indicator_);
541  main_layout->addWidget(waypoint_status_indicator_);
542  main_layout->addWidget(pause_resume_button_);
543  main_layout->addWidget(start_reset_button_);
544  main_layout->addWidget(navigation_mode_button_);
545 
546  QHBoxLayout * group_box_l1_layout = new QHBoxLayout;
547  QHBoxLayout * group_box_l2_layout = new QHBoxLayout;
548 
549  group_box_l1_layout->addWidget(save_waypoints_button_);
550  group_box_l1_layout->addWidget(load_waypoints_button_);
551  group_box_l1_layout->addWidget(pause_waypoint_button_);
552 
553  group_box_l2_layout->addWidget(number_of_loops_);
554  group_box_l2_layout->addWidget(nr_of_loops_);
555  group_box_l2_layout->addWidget(store_initial_pose_checkbox_);
556 
557  group_box_layout->addLayout(group_box_l1_layout);
558  group_box_layout->addLayout(group_box_l2_layout);
559 
560  groupBox->setLayout(group_box_layout);
561 
562  main_layout->addWidget(groupBox);
563  main_layout->setContentsMargins(10, 10, 10, 10);
564  setLayout(main_layout);
565 
566  navigation_action_client_ =
567  rclcpp_action::create_client<nav2_msgs::action::NavigateToPose>(
568  client_node_,
569  "navigate_to_pose");
570  waypoint_follower_action_client_ =
571  rclcpp_action::create_client<nav2_msgs::action::FollowWaypoints>(
572  client_node_,
573  "follow_waypoints");
574  nav_through_poses_action_client_ =
575  rclcpp_action::create_client<nav2_msgs::action::NavigateThroughPoses>(
576  client_node_,
577  "navigate_through_poses");
578 
579  // Setting up tf for initial pose
580  tf2_buffer_ = std::make_shared<tf2_ros::Buffer>(client_node_->get_clock());
581  auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
582  client_node_->get_node_base_interface(),
583  client_node_->get_node_timers_interface());
584  tf2_buffer_->setCreateTimerInterface(timer_interface);
585  transform_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
586 
587  navigation_goal_ = nav2_msgs::action::NavigateToPose::Goal();
588  waypoint_follower_goal_ = nav2_msgs::action::FollowWaypoints::Goal();
589  nav_through_poses_goal_ = nav2_msgs::action::NavigateThroughPoses::Goal();
590 
591  wp_navigation_markers_pub_ =
592  client_node_->create_publisher<visualization_msgs::msg::MarkerArray>(
593  "waypoints",
594  rclcpp::QoS(1).transient_local());
595 
596  QObject::connect(
597  &GoalUpdater, SIGNAL(updateGoal(double,double,double,QString)), // NOLINT
598  this, SLOT(onNewGoal(double,double,double,QString))); // NOLINT
599 }
600 
601 Nav2Panel::~Nav2Panel()
602 {
603 }
604 
605 void Nav2Panel::initialStateHandler()
606 {
607  if (store_initial_pose_checkbox_->isChecked()) {
608  store_initial_pose_ = true;
609 
610  } else {
611  store_initial_pose_ = false;
612  }
613 }
614 
615 bool Nav2Panel::isLoopValueValid(std::string & loop_value)
616 {
617  // Check for just empty space
618  if (loop_value.empty()) {
619  std::cout << "Loop value cannot be set to empty, setting to 0" << std::endl;
620  loop_value = "0";
621  nr_of_loops_->setText("0");
622  return true;
623  }
624 
625  // Check for any chars or spaces in the string
626  for (char & c : loop_value) {
627  if (isalpha(c) || isspace(c) || ispunct(c)) {
628  waypoint_status_indicator_->setText(
629  "<b> Note: </b> Set a valid value for the loop");
630  std::cout << "Set a valid value for the loop, check for alphabets and spaces" << std::endl;
631  navigation_mode_button_->setEnabled(false);
632  return false;
633  }
634  }
635 
636  try {
637  stoi(loop_value);
638  } catch (std::invalid_argument const & ex) {
639  // Handling special symbols
640  waypoint_status_indicator_->setText("<b> Note: </b> Set a valid value for the loop");
641  navigation_mode_button_->setEnabled(false);
642  return false;
643  } catch (std::out_of_range const & ex) {
644  // Handling out of range values
645  waypoint_status_indicator_->setText(
646  "<b> Note: </b> Loop value out of range, setting max possible value"
647  );
648  loop_value = std::to_string(std::numeric_limits<int>::max());
649  nr_of_loops_->setText(QString::fromStdString(loop_value));
650  }
651  return true;
652 }
653 
654 void Nav2Panel::loophandler()
655 {
656  loop_no_ = nr_of_loops_->displayText().toStdString();
657 
658  // Sanity check for the loop value
659  if (!isLoopValueValid(loop_no_)) {
660  return;
661  }
662 
663  // Enabling the start_waypoint_follow button, if disabled.
664  navigation_mode_button_->setEnabled(true);
665 
666  // Disabling nav_through_poses button
667  if (!loop_no_.empty() && stoi(loop_no_) > 0) {
668  pause_resume_button_->setEnabled(false);
669  } else {
670  pause_resume_button_->setEnabled(true);
671  }
672 }
673 
674 void Nav2Panel::handleGoalLoader()
675 {
676  acummulated_poses_.clear();
677 
678  std::cout << "Loading Waypoints!" << std::endl;
679 
680  QString file = QFileDialog::getOpenFileName(
681  this,
682  tr("Open File"), "",
683  tr("yaml(*.yaml);;All Files (*)"));
684 
685  YAML::Node available_waypoints;
686 
687  try {
688  available_waypoints = YAML::LoadFile(file.toStdString());
689  } catch (const std::exception & ex) {
690  std::cout << ex.what() << ", please select a valid file" << std::endl;
691  updateWpNavigationMarkers();
692  return;
693  }
694 
695  const YAML::Node & waypoint_iter = available_waypoints["waypoints"];
696  for (YAML::const_iterator it = waypoint_iter.begin(); it != waypoint_iter.end(); ++it) {
697  auto waypoint = waypoint_iter[it->first.as<std::string>()];
698  auto pose = waypoint["pose"].as<std::vector<double>>();
699  auto orientation = waypoint["orientation"].as<std::vector<double>>();
700  acummulated_poses_.push_back(convert_to_msg(pose, orientation));
701  }
702 
703  // Publishing Waypoint Navigation marker after loading wp's
704  updateWpNavigationMarkers();
705 }
706 
707 geometry_msgs::msg::PoseStamped Nav2Panel::convert_to_msg(
708  std::vector<double> pose,
709  std::vector<double> orientation)
710 {
711  auto msg = geometry_msgs::msg::PoseStamped();
712 
713  msg.header.frame_id = "map";
714  // msg.header.stamp = client_node_->now(); // client node doesn't respect sim time yet
715 
716  msg.pose.position.x = pose[0];
717  msg.pose.position.y = pose[1];
718  msg.pose.position.z = pose[2];
719 
720  msg.pose.orientation.w = orientation[0];
721  msg.pose.orientation.x = orientation[1];
722  msg.pose.orientation.y = orientation[2];
723  msg.pose.orientation.z = orientation[3];
724 
725  return msg;
726 }
727 
728 void Nav2Panel::handleGoalSaver()
729 {
730 // Check if the waypoints are accumulated
731 
732  if (acummulated_poses_.empty()) {
733  std::cout << "No accumulated Points to Save!" << std::endl;
734  return;
735  } else {
736  std::cout << "Saving Waypoints!" << std::endl;
737  }
738 
739  YAML::Emitter out;
740  out << YAML::BeginMap;
741  out << YAML::Key << "waypoints";
742  out << YAML::BeginMap;
743 
744  // Save WPs to data structure
745  for (unsigned int i = 0; i < acummulated_poses_.size(); ++i) {
746  out << YAML::Key << "waypoint" + std::to_string(i);
747  out << YAML::BeginMap;
748  out << YAML::Key << "pose";
749  std::vector<double> pose =
750  {acummulated_poses_[i].pose.position.x, acummulated_poses_[i].pose.position.y,
751  acummulated_poses_[i].pose.position.z};
752  out << YAML::Value << pose;
753  out << YAML::Key << "orientation";
754  std::vector<double> orientation =
755  {acummulated_poses_[i].pose.orientation.w, acummulated_poses_[i].pose.orientation.x,
756  acummulated_poses_[i].pose.orientation.y, acummulated_poses_[i].pose.orientation.z};
757  out << YAML::Value << orientation;
758  out << YAML::EndMap;
759  }
760 
761  // open dialog to save it in a file
762  QString file = QFileDialog::getSaveFileName(
763  this,
764  tr("Open File"), "",
765  tr("yaml(*.yaml);;All Files (*)"));
766 
767  if (!file.toStdString().empty()) {
768  std::ofstream fout(file.toStdString() + ".yaml");
769  fout << out.c_str();
770  std::cout << "Saving waypoints succeeded" << std::endl;
771  } else {
772  std::cout << "Saving waypoints aborted" << std::endl;
773  }
774 }
775 
776 void
777 Nav2Panel::onInitialize()
778 {
779  auto node = getDisplayContext()->getRosNodeAbstraction().lock()->get_raw_node();
780 
781  // declaring parameter to get the base frame
782  node->declare_parameter("base_frame", rclcpp::ParameterValue(std::string("base_footprint")));
783  node->get_parameter("base_frame", base_frame_);
784 
785  // create action feedback subscribers
786  navigation_feedback_sub_ =
787  node->create_subscription<nav2_msgs::action::NavigateToPose::Impl::FeedbackMessage>(
788  "navigate_to_pose/_action/feedback",
789  rclcpp::SystemDefaultsQoS(),
790  [this](const nav2_msgs::action::NavigateToPose::Impl::FeedbackMessage::SharedPtr msg) {
791  if (stoi(nr_of_loops_->displayText().toStdString()) > 0) {
792  if (goal_index_ == 0 && !loop_counter_stop_) {
793  loop_count_++;
794  loop_counter_stop_ = true;
795  }
796  if (goal_index_ != 0) {
797  loop_counter_stop_ = false;
798  }
799  navigation_feedback_indicator_->setText(
800  getNavToPoseFeedbackLabel(msg->feedback) + QString(
801  std::string(
802  "</td></tr><tr><td width=150>Waypoint:</td><td>" +
803  toString(goal_index_ + 1)).c_str()) + QString(
804  std::string(
805  "</td></tr><tr><td width=150>Loop:</td><td>" +
806  toString(loop_count_)).c_str()));
807  } else {
808  navigation_feedback_indicator_->setText(getNavToPoseFeedbackLabel(msg->feedback));
809  }
810  });
811  nav_through_poses_feedback_sub_ =
812  node->create_subscription<nav2_msgs::action::NavigateThroughPoses::Impl::FeedbackMessage>(
813  "navigate_through_poses/_action/feedback",
814  rclcpp::SystemDefaultsQoS(),
815  [this](const nav2_msgs::action::NavigateThroughPoses::Impl::FeedbackMessage::SharedPtr msg) {
816  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel(msg->feedback));
817  });
818 
819  // create action goal status subscribers
820  navigation_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
821  "navigate_to_pose/_action/status",
822  rclcpp::SystemDefaultsQoS(),
823  [this](const action_msgs::msg::GoalStatusArray::SharedPtr msg) {
824  navigation_goal_status_indicator_->setText(
825  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
826  // Clearing all the stored values once reaching the final goal
827  if (
828  loop_count_ == stoi(nr_of_loops_->displayText().toStdString()) &&
829  goal_index_ == static_cast<int>(store_poses_.size()) - 1 &&
830  msg->status_list.back().status == action_msgs::msg::GoalStatus::STATUS_SUCCEEDED)
831  {
832  store_poses_.clear();
833  waypoint_status_indicator_->clear();
834  loop_no_ = "0";
835  loop_count_ = 0;
836  navigation_feedback_indicator_->setText(getNavToPoseFeedbackLabel());
837  }
838  });
839  nav_through_poses_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
840  "navigate_through_poses/_action/status",
841  rclcpp::SystemDefaultsQoS(),
842  [this](const action_msgs::msg::GoalStatusArray::SharedPtr msg) {
843  navigation_goal_status_indicator_->setText(
844  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
845  if (msg->status_list.back().status != action_msgs::msg::GoalStatus::STATUS_EXECUTING) {
846  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
847  }
848  });
849 }
850 
851 void
852 Nav2Panel::startThread()
853 {
854  // start initial thread now that state machine is started
855  initial_thread_->start();
856 }
857 
858 void
859 Nav2Panel::onPause()
860 {
861  QFuture<void> futureNav =
862  QtConcurrent::run(
863  std::bind(
865  client_nav_.get(), std::placeholders::_1), server_timeout_);
866  QFuture<void> futureLoc =
867  QtConcurrent::run(
868  std::bind(
870  client_loc_.get(), std::placeholders::_1), server_timeout_);
871 }
872 
873 void
874 Nav2Panel::onResume()
875 {
876  QFuture<void> futureNav =
877  QtConcurrent::run(
878  std::bind(
880  client_nav_.get(), std::placeholders::_1), server_timeout_);
881  QFuture<void> futureLoc =
882  QtConcurrent::run(
883  std::bind(
885  client_loc_.get(), std::placeholders::_1), server_timeout_);
886 }
887 
888 void
889 Nav2Panel::onStartup()
890 {
891  QFuture<void> futureNav =
892  QtConcurrent::run(
893  std::bind(
895  client_nav_.get(), std::placeholders::_1), server_timeout_);
896  QFuture<void> futureLoc =
897  QtConcurrent::run(
898  std::bind(
900  client_loc_.get(), std::placeholders::_1), server_timeout_);
901 }
902 
903 void
904 Nav2Panel::onShutdown()
905 {
906  QFuture<void> futureNav =
907  QtConcurrent::run(
908  std::bind(
910  client_nav_.get(), std::placeholders::_1), server_timeout_);
911  QFuture<void> futureLoc =
912  QtConcurrent::run(
913  std::bind(
915  client_loc_.get(), std::placeholders::_1), server_timeout_);
916  timer_.stop();
917 }
918 
919 void
920 Nav2Panel::onCancel()
921 {
922  QFuture<void> future =
923  QtConcurrent::run(
924  std::bind(
925  &Nav2Panel::onCancelButtonPressed,
926  this));
927  waypoint_status_indicator_->clear();
928  store_poses_.clear();
929  acummulated_poses_.clear();
930 }
931 
932 void Nav2Panel::onResumedWp()
933 {
934  QFuture<void> future =
935  QtConcurrent::run(
936  std::bind(
937  &Nav2Panel::onCancelButtonPressed,
938  this));
939  acummulated_poses_ = store_poses_;
940  loop_no_ = std::to_string(
941  stoi(nr_of_loops_->displayText().toStdString()) -
942  loop_count_);
943  waypoint_status_indicator_->setText(
944  QString(std::string("<b> Note: </b> Navigation is paused.").c_str()));
945 }
946 
947 void
948 Nav2Panel::onNewGoal(double x, double y, double theta, QString frame)
949 {
950  auto pose = geometry_msgs::msg::PoseStamped();
951 
952  // pose.header.stamp = client_node_->now(); // client node doesn't respect sim time yet
953  pose.header.frame_id = frame.toStdString();
954  pose.pose.position.x = x;
955  pose.pose.position.y = y;
956  pose.pose.position.z = 0.0;
957  pose.pose.orientation = orientationAroundZAxis(theta);
958 
959  if (store_poses_.empty()) {
960  if (state_machine_.configuration().contains(accumulating_)) {
961  waypoint_status_indicator_->clear();
962  acummulated_poses_.push_back(pose);
963  } else {
964  acummulated_poses_.clear();
965  updateWpNavigationMarkers();
966  std::cout << "Start navigation" << std::endl;
967  startNavigation(pose);
968  }
969  } else {
970  waypoint_status_indicator_->setText(
971  QString(std::string("<b> Note: </b> Cannot set goal in pause state").c_str()));
972  }
973  updateWpNavigationMarkers();
974 }
975 
976 void
977 Nav2Panel::onCancelButtonPressed()
978 {
979  if (navigation_goal_handle_) {
980  auto future_cancel = navigation_action_client_->async_cancel_goal(navigation_goal_handle_);
981 
982  if (rclcpp::spin_until_future_complete(client_node_, future_cancel, server_timeout_) !=
983  rclcpp::FutureReturnCode::SUCCESS)
984  {
985  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel goal");
986  } else {
987  navigation_goal_handle_.reset();
988  }
989  }
990 
991  if (waypoint_follower_goal_handle_) {
992  auto future_cancel =
993  waypoint_follower_action_client_->async_cancel_goal(waypoint_follower_goal_handle_);
994 
995  if (rclcpp::spin_until_future_complete(client_node_, future_cancel, server_timeout_) !=
996  rclcpp::FutureReturnCode::SUCCESS)
997  {
998  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel waypoint follower");
999  } else {
1000  waypoint_follower_goal_handle_.reset();
1001  }
1002  }
1003 
1004  if (nav_through_poses_goal_handle_) {
1005  auto future_cancel =
1006  nav_through_poses_action_client_->async_cancel_goal(nav_through_poses_goal_handle_);
1007 
1008  if (rclcpp::spin_until_future_complete(client_node_, future_cancel, server_timeout_) !=
1009  rclcpp::FutureReturnCode::SUCCESS)
1010  {
1011  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel nav through pose action");
1012  } else {
1013  nav_through_poses_goal_handle_.reset();
1014  }
1015  }
1016 
1017  timer_.stop();
1018 }
1019 
1020 void
1021 Nav2Panel::onAccumulatedWp()
1022 {
1023  if (acummulated_poses_.empty()) {
1024  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1025  waypoint_status_indicator_->setText(
1026  "<b> Note: </b> Uh oh! Someone forgot to select the waypoints");
1027  return;
1028  }
1029 
1030  // Sanity check for the loop value
1031  if (!isLoopValueValid(loop_no_)) {
1032  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1033  return;
1034  }
1035 
1036  // Remove any warning status at this point
1037  waypoint_status_indicator_->clear();
1038 
1039  // Disable navigation modes
1040  navigation_mode_button_->setEnabled(false);
1041  pause_resume_button_->setEnabled(false);
1042 
1045  if (store_poses_.empty()) {
1046  std::cout << "Start waypoint" << std::endl;
1047 
1048  // Setting the final loop value on the text box for sanity
1049  nr_of_loops_->setText(QString::fromStdString(loop_no_));
1050 
1051  // Variable to store initial pose
1052  geometry_msgs::msg::TransformStamped init_transform;
1053 
1054  // Looking up transform to get initial pose
1055  if (store_initial_pose_) {
1056  try {
1057  init_transform = tf2_buffer_->lookupTransform(
1058  acummulated_poses_[0].header.frame_id, base_frame_,
1059  tf2::TimePointZero);
1060  } catch (const tf2::TransformException & ex) {
1061  RCLCPP_INFO(
1062  client_node_->get_logger(), "Could not transform %s to %s: %s",
1063  acummulated_poses_[0].header.frame_id.c_str(), base_frame_.c_str(), ex.what());
1064  return;
1065  }
1066 
1067  // Converting TransformStamped to PoseStamped
1068  geometry_msgs::msg::PoseStamped initial_pose;
1069  initial_pose.header = init_transform.header;
1070  initial_pose.pose.position.x = init_transform.transform.translation.x;
1071  initial_pose.pose.position.y = init_transform.transform.translation.y;
1072  initial_pose.pose.position.z = init_transform.transform.translation.z;
1073  initial_pose.pose.orientation.x = init_transform.transform.rotation.x;
1074  initial_pose.pose.orientation.y = init_transform.transform.rotation.y;
1075  initial_pose.pose.orientation.z = init_transform.transform.rotation.z;
1076  initial_pose.pose.orientation.w = init_transform.transform.rotation.w;
1077 
1078  // inserting the acummulated pose
1079  acummulated_poses_.insert(acummulated_poses_.begin(), initial_pose);
1080  updateWpNavigationMarkers();
1081  initial_pose_stored_ = true;
1082  if (loop_count_ == 0) {
1083  goal_index_ = 1;
1084  }
1085  } else if (!store_initial_pose_ && initial_pose_stored_) {
1086  acummulated_poses_.erase(
1087  acummulated_poses_.begin(),
1088  acummulated_poses_.begin());
1089  }
1090  } else {
1091  std::cout << "Resuming waypoint" << std::endl;
1092  }
1093 
1094  startWaypointFollowing(acummulated_poses_);
1095  store_poses_ = acummulated_poses_;
1096  acummulated_poses_.clear();
1097 }
1098 
1099 void
1100 Nav2Panel::onAccumulatedNTP()
1101 {
1102  std::cout << "Start navigate through poses" << std::endl;
1103  startNavThroughPoses(acummulated_poses_);
1104 }
1105 
1106 void
1107 Nav2Panel::onAccumulating()
1108 {
1109  acummulated_poses_.clear();
1110  store_poses_.clear();
1111  loop_count_ = 0;
1112  loop_no_ = "0";
1113  initial_pose_stored_ = false;
1114  loop_counter_stop_ = true;
1115  goal_index_ = 0;
1116  updateWpNavigationMarkers();
1117 }
1118 
1119 void
1120 Nav2Panel::timerEvent(QTimerEvent * event)
1121 {
1122  if (state_machine_.configuration().contains(accumulated_wp_)) {
1123  if (event->timerId() == timer_.timerId()) {
1124  if (!waypoint_follower_goal_handle_) {
1125  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1126  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1127  return;
1128  }
1129 
1130  rclcpp::spin_some(client_node_);
1131  auto status = waypoint_follower_goal_handle_->get_status();
1132 
1133  // Check if the goal is still executing
1134  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1135  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1136  {
1137  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1138  } else {
1139  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1140  timer_.stop();
1141  }
1142  }
1143  } else if (state_machine_.configuration().contains(accumulated_nav_through_poses_)) {
1144  if (event->timerId() == timer_.timerId()) {
1145  if (!nav_through_poses_goal_handle_) {
1146  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1147  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1148  return;
1149  }
1150 
1151  rclcpp::spin_some(client_node_);
1152  auto status = nav_through_poses_goal_handle_->get_status();
1153 
1154  // Check if the goal is still executing
1155  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1156  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1157  {
1158  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1159  } else {
1160  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1161  timer_.stop();
1162  }
1163  }
1164  } else {
1165  if (event->timerId() == timer_.timerId()) {
1166  if (!navigation_goal_handle_) {
1167  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1168  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1169  return;
1170  }
1171 
1172  rclcpp::spin_some(client_node_);
1173  auto status = navigation_goal_handle_->get_status();
1174 
1175  // Check if the goal is still executing
1176  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1177  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1178  {
1179  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1180  } else {
1181  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1182  timer_.stop();
1183  }
1184  }
1185  }
1186 }
1187 
1188 void
1189 Nav2Panel::startWaypointFollowing(std::vector<geometry_msgs::msg::PoseStamped> poses)
1190 {
1191  auto is_action_server_ready =
1192  waypoint_follower_action_client_->wait_for_action_server(std::chrono::seconds(5));
1193  if (!is_action_server_ready) {
1194  RCLCPP_ERROR(
1195  client_node_->get_logger(), "follow_waypoints action server is not available."
1196  " Is the initial pose set?");
1197  return;
1198  }
1199 
1200  // Send the goal poses
1201  waypoint_follower_goal_.poses = poses;
1202  waypoint_follower_goal_.goal_index = goal_index_;
1203  waypoint_follower_goal_.number_of_loops = stoi(loop_no_);
1204 
1205  RCLCPP_DEBUG(
1206  client_node_->get_logger(), "Sending a path of %zu waypoints:",
1207  waypoint_follower_goal_.poses.size());
1208  for (auto waypoint : waypoint_follower_goal_.poses) {
1209  RCLCPP_DEBUG(
1210  client_node_->get_logger(),
1211  "\t(%lf, %lf)", waypoint.pose.position.x, waypoint.pose.position.y);
1212  }
1213 
1214  // Enable result awareness by providing an empty lambda function
1215  auto send_goal_options =
1216  rclcpp_action::Client<nav2_msgs::action::FollowWaypoints>::SendGoalOptions();
1217  send_goal_options.result_callback = [this](auto) {
1218  waypoint_follower_goal_handle_.reset();
1219  };
1220 
1221  send_goal_options.feedback_callback = [this](
1222  WaypointFollowerGoalHandle::SharedPtr /*goal_handle*/,
1223  const std::shared_ptr<const nav2_msgs::action::FollowWaypoints::Feedback> feedback) {
1224  goal_index_ = feedback->current_waypoint;
1225  };
1226 
1227  auto future_goal_handle =
1228  waypoint_follower_action_client_->async_send_goal(waypoint_follower_goal_, send_goal_options);
1229  if (rclcpp::spin_until_future_complete(client_node_, future_goal_handle, server_timeout_) !=
1230  rclcpp::FutureReturnCode::SUCCESS)
1231  {
1232  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1233  return;
1234  }
1235 
1236  // Get the goal handle and save so that we can check on completion in the timer callback
1237  waypoint_follower_goal_handle_ = future_goal_handle.get();
1238  if (!waypoint_follower_goal_handle_) {
1239  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1240  return;
1241  }
1242 
1243  timer_.start(200, this);
1244 }
1245 
1246 void
1247 Nav2Panel::startNavThroughPoses(std::vector<geometry_msgs::msg::PoseStamped> poses)
1248 {
1249  auto is_action_server_ready =
1250  nav_through_poses_action_client_->wait_for_action_server(std::chrono::seconds(5));
1251  if (!is_action_server_ready) {
1252  RCLCPP_ERROR(
1253  client_node_->get_logger(), "navigate_through_poses action server is not available."
1254  " Is the initial pose set?");
1255  return;
1256  }
1257 
1258  nav_through_poses_goal_.poses = poses;
1259  RCLCPP_INFO(
1260  client_node_->get_logger(),
1261  "NavigateThroughPoses will be called using the BT Navigator's default behavior tree.");
1262 
1263  RCLCPP_DEBUG(
1264  client_node_->get_logger(), "Sending a path of %zu waypoints:",
1265  nav_through_poses_goal_.poses.size());
1266  for (auto waypoint : nav_through_poses_goal_.poses) {
1267  RCLCPP_DEBUG(
1268  client_node_->get_logger(),
1269  "\t(%lf, %lf)", waypoint.pose.position.x, waypoint.pose.position.y);
1270  }
1271 
1272  // Enable result awareness by providing an empty lambda function
1273  auto send_goal_options =
1274  rclcpp_action::Client<nav2_msgs::action::NavigateThroughPoses>::SendGoalOptions();
1275  send_goal_options.result_callback = [this](auto) {
1276  nav_through_poses_goal_handle_.reset();
1277  };
1278 
1279  auto future_goal_handle =
1280  nav_through_poses_action_client_->async_send_goal(nav_through_poses_goal_, send_goal_options);
1281  if (rclcpp::spin_until_future_complete(client_node_, future_goal_handle, server_timeout_) !=
1282  rclcpp::FutureReturnCode::SUCCESS)
1283  {
1284  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1285  return;
1286  }
1287 
1288  // Get the goal handle and save so that we can check on completion in the timer callback
1289  nav_through_poses_goal_handle_ = future_goal_handle.get();
1290  if (!nav_through_poses_goal_handle_) {
1291  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1292  return;
1293  }
1294 
1295  timer_.start(200, this);
1296 }
1297 
1298 void
1299 Nav2Panel::startNavigation(geometry_msgs::msg::PoseStamped pose)
1300 {
1301  auto is_action_server_ready =
1302  navigation_action_client_->wait_for_action_server(std::chrono::seconds(5));
1303  if (!is_action_server_ready) {
1304  RCLCPP_ERROR(
1305  client_node_->get_logger(),
1306  "navigate_to_pose action server is not available."
1307  " Is the initial pose set?");
1308  return;
1309  }
1310 
1311  // Send the goal pose
1312  navigation_goal_.pose = pose;
1313 
1314  RCLCPP_INFO(
1315  client_node_->get_logger(),
1316  "NavigateToPose will be called using the BT Navigator's default behavior tree.");
1317 
1318  // Enable result awareness by providing an empty lambda function
1319  auto send_goal_options =
1320  rclcpp_action::Client<nav2_msgs::action::NavigateToPose>::SendGoalOptions();
1321  send_goal_options.result_callback = [this](auto) {
1322  navigation_goal_handle_.reset();
1323  };
1324 
1325  auto future_goal_handle =
1326  navigation_action_client_->async_send_goal(navigation_goal_, send_goal_options);
1327  if (rclcpp::spin_until_future_complete(client_node_, future_goal_handle, server_timeout_) !=
1328  rclcpp::FutureReturnCode::SUCCESS)
1329  {
1330  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1331  return;
1332  }
1333 
1334  // Get the goal handle and save so that we can check on completion in the timer callback
1335  navigation_goal_handle_ = future_goal_handle.get();
1336  if (!navigation_goal_handle_) {
1337  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1338  return;
1339  }
1340 
1341  timer_.start(200, this);
1342 }
1343 
1344 void
1345 Nav2Panel::save(rviz_common::Config config) const
1346 {
1347  Panel::save(config);
1348 }
1349 
1350 void
1351 Nav2Panel::load(const rviz_common::Config & config)
1352 {
1353  Panel::load(config);
1354 }
1355 
1356 void
1357 Nav2Panel::resetUniqueId()
1358 {
1359  unique_id = 0;
1360 }
1361 
1362 int
1363 Nav2Panel::getUniqueId()
1364 {
1365  int temp_id = unique_id;
1366  unique_id += 1;
1367  return temp_id;
1368 }
1369 
1370 void
1371 Nav2Panel::updateWpNavigationMarkers()
1372 {
1373  resetUniqueId();
1374 
1375  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
1376 
1377  for (size_t i = 0; i < acummulated_poses_.size(); i++) {
1378  // Draw a green arrow at the waypoint pose
1379  visualization_msgs::msg::Marker arrow_marker;
1380  arrow_marker.header = acummulated_poses_[i].header;
1381  arrow_marker.id = getUniqueId();
1382  arrow_marker.type = visualization_msgs::msg::Marker::ARROW;
1383  arrow_marker.action = visualization_msgs::msg::Marker::ADD;
1384  arrow_marker.pose = acummulated_poses_[i].pose;
1385  arrow_marker.scale.x = 0.3;
1386  arrow_marker.scale.y = 0.05;
1387  arrow_marker.scale.z = 0.02;
1388  arrow_marker.color.r = 0;
1389  arrow_marker.color.g = 255;
1390  arrow_marker.color.b = 0;
1391  arrow_marker.color.a = 1.0f;
1392  arrow_marker.lifetime = rclcpp::Duration(0s);
1393  arrow_marker.frame_locked = false;
1394  marker_array->markers.push_back(arrow_marker);
1395 
1396  // Draw a red circle at the waypoint pose
1397  visualization_msgs::msg::Marker circle_marker;
1398  circle_marker.header = acummulated_poses_[i].header;
1399  circle_marker.id = getUniqueId();
1400  circle_marker.type = visualization_msgs::msg::Marker::SPHERE;
1401  circle_marker.action = visualization_msgs::msg::Marker::ADD;
1402  circle_marker.pose = acummulated_poses_[i].pose;
1403  circle_marker.scale.x = 0.05;
1404  circle_marker.scale.y = 0.05;
1405  circle_marker.scale.z = 0.05;
1406  circle_marker.color.r = 255;
1407  circle_marker.color.g = 0;
1408  circle_marker.color.b = 0;
1409  circle_marker.color.a = 1.0f;
1410  circle_marker.lifetime = rclcpp::Duration(0s);
1411  circle_marker.frame_locked = false;
1412  marker_array->markers.push_back(circle_marker);
1413 
1414  // Draw the waypoint number
1415  visualization_msgs::msg::Marker marker_text;
1416  marker_text.header = acummulated_poses_[i].header;
1417  marker_text.id = getUniqueId();
1418  marker_text.type = visualization_msgs::msg::Marker::TEXT_VIEW_FACING;
1419  marker_text.action = visualization_msgs::msg::Marker::ADD;
1420  marker_text.pose = acummulated_poses_[i].pose;
1421  marker_text.pose.position.z += 0.2; // draw it on top of the waypoint
1422  marker_text.scale.x = 0.07;
1423  marker_text.scale.y = 0.07;
1424  marker_text.scale.z = 0.07;
1425  marker_text.color.r = 0;
1426  marker_text.color.g = 255;
1427  marker_text.color.b = 0;
1428  marker_text.color.a = 1.0f;
1429  marker_text.lifetime = rclcpp::Duration(0s);
1430  marker_text.frame_locked = false;
1431  marker_text.text = "wp_" + std::to_string(i + 1);
1432  marker_array->markers.push_back(marker_text);
1433  }
1434 
1435  if (marker_array->markers.empty()) {
1436  visualization_msgs::msg::Marker clear_all_marker;
1437  clear_all_marker.action = visualization_msgs::msg::Marker::DELETEALL;
1438  marker_array->markers.push_back(clear_all_marker);
1439  }
1440 
1441  wp_navigation_markers_pub_->publish(std::move(marker_array));
1442 }
1443 
1444 inline QString
1445 Nav2Panel::getNavToPoseFeedbackLabel(nav2_msgs::action::NavigateToPose::Feedback msg)
1446 {
1447  return QString(std::string("<table>" + toLabel(msg) + "</table>").c_str());
1448 }
1449 
1450 inline QString
1451 Nav2Panel::getNavThroughPosesFeedbackLabel(nav2_msgs::action::NavigateThroughPoses::Feedback msg)
1452 {
1453  return QString(
1454  std::string(
1455  "<table><tr><td width=150>Poses remaining:</td><td>" +
1456  std::to_string(msg.number_of_poses_remaining) +
1457  "</td></tr>" + toLabel(msg) + "</table>").c_str());
1458 }
1459 
1460 template<typename T>
1461 inline std::string Nav2Panel::toLabel(T & msg)
1462 {
1463  return std::string(
1464  "<tr><td width=150>ETA:</td><td>" +
1465  toString(rclcpp::Duration(msg.estimated_time_remaining).seconds(), 0) + " s"
1466  "</td></tr><tr><td width=150>Distance remaining:</td><td>" +
1467  toString(msg.distance_remaining, 2) + " m"
1468  "</td></tr><tr><td width=150>Time taken:</td><td>" +
1469  toString(rclcpp::Duration(msg.navigation_time).seconds(), 0) + " s"
1470  "</td></tr><tr><td width=150>Recoveries:</td><td>" +
1471  std::to_string(msg.number_of_recoveries) +
1472  "</td></tr>");
1473 }
1474 
1475 inline std::string
1476 Nav2Panel::toString(double val, int precision)
1477 {
1478  std::ostringstream out;
1479  out.precision(precision);
1480  out << std::fixed << val;
1481  return out.str();
1482 }
1483 
1484 } // namespace nav2_rviz_plugins
1485 
1486 #include <pluginlib/class_list_macros.hpp> // NOLINT
1487 PLUGINLIB_EXPORT_CLASS(nav2_rviz_plugins::Nav2Panel, rviz_common::Panel)
bool pause(const std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1))
Make pause service call.
bool reset(const std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1))
Make reset service call.
bool startup(const std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1))
Make start up service call.
bool resume(const std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1))
Make resume service call.
Panel to interface to the nav2 stack.
Definition: nav2_panel.hpp:50