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