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