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 <string>
29 
30 #include "nav2_rviz_plugins/goal_common.hpp"
31 #include "nav2_rviz_plugins/utils.hpp"
32 #include "rclcpp/rclcpp.hpp"
33 #include "rviz_common/display_context.hpp"
34 #include "rviz_common/load_resource.hpp"
35 #include "yaml-cpp/yaml.h"
36 #include "geometry_msgs/msg/pose.hpp"
37 
38 using namespace std::chrono_literals;
39 
40 namespace nav2_rviz_plugins
41 {
42 using nav2_util::geometry_utils::orientationAroundZAxis;
43 
44 // Define global GoalPoseUpdater so that the nav2 GoalTool plugin can access to update goal pose
45 GoalPoseUpdater GoalUpdater;
46 
47 Nav2Panel::Nav2Panel(QWidget * parent)
48 : Panel(parent),
49  server_timeout_(100)
50 {
51  // Create the control button and its tooltip
52 
53  start_reset_button_ = new QPushButton;
54  pause_resume_button_ = new QPushButton;
55  navigation_mode_button_ = new QPushButton;
56  add_pose_button_ = new QPushButton;
57  remove_pose_button_ = new QPushButton;
58  save_waypoints_button_ = new QPushButton;
59  load_waypoints_button_ = new QPushButton;
60  pause_waypoint_button_ = new QPushButton;
61  start_nav_to_pose_button_ = new QPushButton;
62  navigation_status_indicator_ = new QLabel;
63  localization_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  const QString localization_active("<table><tr><td width=150><b>Localization:</b></td>"
95  "<td><font color=green>active</color></td></tr></table>");
96  const QString localization_inactive("<table><tr><td width=150><b>Localization:</b></td>"
97  "<td>inactive</td></tr></table>");
98  const QString localization_unknown("<table><tr><td width=150><b>Localization:</b></td>"
99  "<td>unknown</td></tr></table>");
100 
101  navigation_status_indicator_->setText(navigation_unknown);
102  localization_status_indicator_->setText(localization_unknown);
103  navigation_goal_status_indicator_->setText(nav2_rviz_plugins::getGoalStatusLabel());
104  number_of_loops_->setText("Num of loops");
105  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
106  navigation_status_indicator_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
107  localization_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_navigation", client_node_);
496  client_loc_ = std::make_shared<nav2_lifecycle_manager::LifecycleManagerClient>(
497  "lifecycle_manager_localization", client_node_);
498  initial_thread_ = new InitialThread(client_nav_, client_loc_);
499  connect(initial_thread_, &InitialThread::finished, initial_thread_, &QObject::deleteLater);
500 
501  QSignalTransition * activeSignal = new QSignalTransition(
502  initial_thread_,
503  &InitialThread::navigationActive);
504  activeSignal->setTargetState(idle_);
505  pre_initial_->addTransition(activeSignal);
506  QSignalTransition * inactiveSignal = new QSignalTransition(
507  initial_thread_,
508  &InitialThread::navigationInactive);
509  inactiveSignal->setTargetState(initial_);
510  pre_initial_->addTransition(inactiveSignal);
511 
512  QObject::connect(
513  initial_thread_, &InitialThread::navigationActive,
514  [this, navigation_active] {
515  navigation_status_indicator_->setText(navigation_active);
516  });
517  QObject::connect(
518  initial_thread_, &InitialThread::navigationInactive,
519  [this, navigation_inactive] {
520  navigation_status_indicator_->setText(navigation_inactive);
521  navigation_goal_status_indicator_->setText(nav2_rviz_plugins::getGoalStatusLabel());
522  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
523  });
524  QObject::connect(
525  initial_thread_, &InitialThread::localizationActive,
526  [this, localization_active] {
527  localization_status_indicator_->setText(localization_active);
528  });
529  QObject::connect(
530  initial_thread_, &InitialThread::localizationInactive,
531  [this, localization_inactive] {
532  localization_status_indicator_->setText(localization_inactive);
533  });
534 
535  state_machine_.addState(pre_initial_);
536  state_machine_.addState(initial_);
537  state_machine_.addState(idle_);
538  state_machine_.addState(running_);
539  state_machine_.addState(canceled_);
540  state_machine_.addState(reset_);
541  state_machine_.addState(paused_);
542  state_machine_.addState(resumed_);
543  state_machine_.addState(accumulating_);
544  state_machine_.addState(accumulated_wp_);
545  state_machine_.addState(accumulated_nav_through_poses_);
546  state_machine_.addState(resumed_wp_);
547 
548  state_machine_.setInitialState(pre_initial_);
549 
550  // delay starting initial thread until state machine has started or a race occurs
551  QObject::connect(&state_machine_, SIGNAL(started()), this, SLOT(startThread()));
552  state_machine_.start();
553 
554  // Lay out the items in the panel
555  QVBoxLayout * main_layout = new QVBoxLayout;
556  QHBoxLayout * side_layout = new QHBoxLayout;
557  QVBoxLayout * status_layout = new QVBoxLayout;
558  QHBoxLayout * logo_layout = new QHBoxLayout;
559 
560  imgDisplayLabel_ = new QLabel("");
561  imgDisplayLabel_->setPixmap(
562  rviz_common::loadPixmap("package://nav2_rviz_plugins/icons/classes/nav2_logo_small.png"));
563 
564  status_layout->addWidget(navigation_status_indicator_);
565  status_layout->addWidget(localization_status_indicator_);
566  status_layout->addWidget(navigation_goal_status_indicator_);
567 
568  logo_layout->addWidget(imgDisplayLabel_, 5, Qt::AlignRight);
569 
570  side_layout->addLayout(status_layout);
571  side_layout->addLayout(logo_layout);
572 
573  main_layout->addLayout(side_layout);
574  main_layout->addWidget(navigation_feedback_indicator_);
575  main_layout->addWidget(waypoint_status_indicator_);
576 
577  // Behavior Tree XML file selection
578  QHBoxLayout * bt_layout = new QHBoxLayout;
579  QLabel * bt_label = new QLabel("Behavior Tree XML:");
580  bt_layout->addWidget(bt_label);
581  bt_layout->addWidget(behavior_tree_file_);
582  main_layout->addLayout(bt_layout);
583 
584  main_layout->addWidget(pause_resume_button_);
585  main_layout->addWidget(start_reset_button_);
586  main_layout->addWidget(navigation_mode_button_);
587 
588  // Tab widget for tools
589  tools_tab_widget_ = new QTabWidget;
590 
591  // Tab 1: NavigateToPose
592  QWidget * nav_to_pose_tab = new QWidget;
593  QGridLayout * nav_to_pose_layout = new QGridLayout;
594 
595  nav_to_pose_layout->addWidget(new QLabel("Frame ID:"), 0, 0);
596  nav_to_pose_frame_id_ = new QLineEdit("map");
597  nav_to_pose_layout->addWidget(nav_to_pose_frame_id_, 0, 1);
598 
599  nav_to_pose_layout->addWidget(new QLabel("Position X:"), 1, 0);
600  nav_to_pose_x_ = new QDoubleSpinBox;
601  nav_to_pose_x_->setRange(-1000.0, 1000.0);
602  nav_to_pose_x_->setDecimals(3);
603  nav_to_pose_x_->setSingleStep(0.1);
604  nav_to_pose_layout->addWidget(nav_to_pose_x_, 1, 1);
605 
606  nav_to_pose_layout->addWidget(new QLabel("Position Y:"), 2, 0);
607  nav_to_pose_y_ = new QDoubleSpinBox;
608  nav_to_pose_y_->setRange(-1000.0, 1000.0);
609  nav_to_pose_y_->setDecimals(3);
610  nav_to_pose_y_->setSingleStep(0.1);
611  nav_to_pose_layout->addWidget(nav_to_pose_y_, 2, 1);
612 
613  nav_to_pose_layout->addWidget(new QLabel("Yaw (radians):"), 3, 0);
614  nav_to_pose_yaw_ = new QDoubleSpinBox;
615  nav_to_pose_yaw_->setRange(-M_PI, M_PI);
616  nav_to_pose_yaw_->setDecimals(4);
617  nav_to_pose_yaw_->setSingleStep(0.01);
618  nav_to_pose_layout->addWidget(nav_to_pose_yaw_, 3, 1);
619 
620  QObject::connect(start_nav_to_pose_button_,
621  &QPushButton::clicked, this, &Nav2Panel::onSendNavToPose);
622  nav_to_pose_layout->addWidget(start_nav_to_pose_button_, 4, 0, 1, 2);
623 
624  nav_to_pose_tab->setLayout(nav_to_pose_layout);
625  tools_tab_widget_->addTab(nav_to_pose_tab, "NavigateToPose");
626 
627  // Tab 2: NavigateThroughPoses / Waypoint Following
628  QWidget * nav_wp_tab = new QWidget;
629  QVBoxLayout * nav_wp_layout = new QVBoxLayout;
630 
631  QLabel * poses_info = new QLabel("Accumulated poses:");
632  nav_wp_layout->addWidget(poses_info);
633 
634  nav_through_poses_tabs_ = new QTabWidget;
635  nav_wp_layout->addWidget(nav_through_poses_tabs_);
636 
637  QHBoxLayout * pose_buttons_layout = new QHBoxLayout;
638  QObject::connect(add_pose_button_, &QPushButton::clicked, this, &Nav2Panel::onAddNavThroughPose);
639  pose_buttons_layout->addWidget(add_pose_button_);
640 
641  QObject::connect(remove_pose_button_,
642  &QPushButton::clicked, this, &Nav2Panel::onRemoveNavThroughPose);
643  pose_buttons_layout->addWidget(remove_pose_button_);
644 
645  nav_wp_layout->addLayout(pose_buttons_layout);
646 
647  QHBoxLayout * file_buttons_layout = new QHBoxLayout;
648  file_buttons_layout->addWidget(save_waypoints_button_);
649  file_buttons_layout->addWidget(load_waypoints_button_);
650  nav_wp_layout->addLayout(file_buttons_layout);
651 
652  // Waypoint Following options section
653  QLabel * wp_options_label = new QLabel("<b>Waypoint Following Options:</b>");
654  nav_wp_layout->addWidget(wp_options_label);
655 
656  QHBoxLayout * wp_controls_layout = new QHBoxLayout;
657  wp_controls_layout->addWidget(pause_waypoint_button_);
658  nav_wp_layout->addLayout(wp_controls_layout);
659 
660  QHBoxLayout * wp_loop_layout = new QHBoxLayout;
661  wp_loop_layout->addWidget(number_of_loops_);
662  wp_loop_layout->addWidget(nr_of_loops_);
663  wp_loop_layout->addWidget(store_initial_pose_checkbox_);
664  nav_wp_layout->addLayout(wp_loop_layout);
665 
666  nav_wp_tab->setLayout(nav_wp_layout);
667  tools_tab_widget_->addTab(nav_wp_tab, "NavigateThroughPoses / Waypoint Following");
668 
669  tools_tab_widget_->setTabEnabled(0, false);
670  tools_tab_widget_->setTabEnabled(1, false);
671 
672  tools_tab_widget_->setCurrentIndex(0);
673 
674  main_layout->addWidget(tools_tab_widget_);
675  main_layout->setContentsMargins(10, 10, 10, 10);
676  setLayout(main_layout);
677 
678  navigation_action_client_ =
679  rclcpp_action::create_client<nav2_msgs::action::NavigateToPose>( // nosemgrep
680  client_node_,
681  "navigate_to_pose");
682  waypoint_follower_action_client_ =
683  rclcpp_action::create_client<nav2_msgs::action::FollowWaypoints>( // nosemgrep
684  client_node_,
685  "follow_waypoints");
686  nav_through_poses_action_client_ =
687  rclcpp_action::create_client<nav2_msgs::action::NavigateThroughPoses>( // nosemgrep
688  client_node_,
689  "navigate_through_poses");
690 
691  // Setting up tf for initial pose
692  tf2_buffer_ = nav2::create_transform_buffer(client_node_);
693  transform_listener_ = nav2::create_transform_listener(*tf2_buffer_);
694 
695  navigation_goal_ = nav2_msgs::action::NavigateToPose::Goal();
696  waypoint_follower_goal_ = nav2_msgs::action::FollowWaypoints::Goal();
697  nav_through_poses_goal_ = nav2_msgs::action::NavigateThroughPoses::Goal();
698 
699  wp_navigation_markers_pub_ =
700  client_node_->create_publisher<visualization_msgs::msg::MarkerArray>(
701  "waypoints",
702  rclcpp::QoS(1).transient_local());
703 
704  QObject::connect(
705  &GoalUpdater, SIGNAL(updateGoal(double,double,double,QString)), // NOLINT
706  this, SLOT(onNewGoal(double,double,double,QString))); // NOLINT
707 }
708 
709 Nav2Panel::~Nav2Panel()
710 {
711 }
712 
713 void Nav2Panel::initialStateHandler()
714 {
715  if (store_initial_pose_checkbox_->isChecked()) {
716  store_initial_pose_ = true;
717 
718  } else {
719  store_initial_pose_ = false;
720  }
721 }
722 
723 bool Nav2Panel::isLoopValueValid(std::string & loop_value)
724 {
725  // Check for just empty space
726  if (loop_value.empty()) {
727  std::cout << "Loop value cannot be set to empty, setting to 0" << std::endl;
728  loop_value = "0";
729  nr_of_loops_->setText("0");
730  return true;
731  }
732 
733  // Check for any chars or spaces in the string
734  for (char & c : loop_value) {
735  if (isalpha(c) || isspace(c) || ispunct(c)) {
736  waypoint_status_indicator_->setText(
737  "<b> Note: </b> Set a valid value for the loop");
738  std::cout << "Set a valid value for the loop, check for alphabets and spaces" << std::endl;
739  navigation_mode_button_->setEnabled(false);
740  return false;
741  }
742  }
743 
744  try {
745  stoi(loop_value);
746  } catch (std::invalid_argument const & ex) {
747  // Handling special symbols
748  waypoint_status_indicator_->setText("<b> Note: </b> Set a valid value for the loop");
749  navigation_mode_button_->setEnabled(false);
750  return false;
751  } catch (std::out_of_range const & ex) {
752  // Handling out of range values
753  waypoint_status_indicator_->setText(
754  "<b> Note: </b> Loop value out of range, setting max possible value"
755  );
756  loop_value = std::to_string(std::numeric_limits<int>::max());
757  nr_of_loops_->setText(QString::fromStdString(loop_value));
758  }
759  return true;
760 }
761 
762 void Nav2Panel::loophandler()
763 {
764  loop_no_ = nr_of_loops_->displayText().toStdString();
765 
766  // Sanity check for the loop value
767  if (!isLoopValueValid(loop_no_)) {
768  return;
769  }
770 
771  // Enabling the start_waypoint_follow button, if disabled.
772  navigation_mode_button_->setEnabled(true);
773 
774  // Disabling nav_through_poses button
775  if (!loop_no_.empty() && stoi(loop_no_) > 0) {
776  pause_resume_button_->setEnabled(false);
777  } else {
778  pause_resume_button_->setEnabled(true);
779  }
780 }
781 
782 void Nav2Panel::handleGoalLoader()
783 {
784  std::cout << "Loading Waypoints!" << std::endl;
785 
786  QString file = QFileDialog::getOpenFileName(
787  this,
788  tr("Open File"), "",
789  tr("yaml(*.yaml);;All Files (*)"));
790 
791  YAML::Node available_waypoints;
792 
793  try {
794  available_waypoints = YAML::LoadFile(file.toStdString());
795  } catch (const std::exception & ex) {
796  std::cout << ex.what() << ", please select a valid file" << std::endl;
797  return;
798  }
799 
800  const YAML::Node & waypoint_iter = available_waypoints["waypoints"];
801  for (YAML::const_iterator it = waypoint_iter.begin(); it != waypoint_iter.end(); ++it) {
802  auto waypoint = waypoint_iter[it->first.as<std::string>()];
803  auto pose = waypoint["pose"].as<std::vector<double>>();
804  auto orientation = waypoint["orientation"].as<std::vector<double>>();
805  acummulated_poses_.goals.push_back(convert_to_msg(pose, orientation));
806  }
807 
808  syncTabsWithAccumulatedPoses();
809  updateWpNavigationMarkers();
810 }
811 
812 geometry_msgs::msg::PoseStamped Nav2Panel::convert_to_msg(
813  std::vector<double> pose,
814  std::vector<double> orientation)
815 {
816  auto msg = geometry_msgs::msg::PoseStamped();
817 
818  msg.header.frame_id = "map";
819  // msg.header.stamp = client_node_->now(); // client node doesn't respect sim time yet
820 
821  msg.pose.position.x = pose[0];
822  msg.pose.position.y = pose[1];
823  msg.pose.position.z = pose[2];
824 
825  msg.pose.orientation.w = orientation[0];
826  msg.pose.orientation.x = orientation[1];
827  msg.pose.orientation.y = orientation[2];
828  msg.pose.orientation.z = orientation[3];
829 
830  return msg;
831 }
832 
833 void Nav2Panel::handleGoalSaver()
834 {
835 // Check if the waypoints are accumulated
836 
837  if (acummulated_poses_.goals.empty()) {
838  std::cout << "No accumulated Points to Save!" << std::endl;
839  return;
840  } else {
841  std::cout << "Saving Waypoints!" << std::endl;
842  }
843 
844  YAML::Emitter out;
845  out << YAML::BeginMap;
846  out << YAML::Key << "waypoints";
847  out << YAML::BeginMap;
848 
849  // Save WPs to data structure
850  for (unsigned int i = 0; i < acummulated_poses_.goals.size(); ++i) {
851  out << YAML::Key << "waypoint" + std::to_string(i);
852  out << YAML::BeginMap;
853  out << YAML::Key << "pose";
854  std::vector<double> pose =
855  {acummulated_poses_.goals[i].pose.position.x, acummulated_poses_.goals[i].pose.position.y,
856  acummulated_poses_.goals[i].pose.position.z};
857  out << YAML::Value << pose;
858  out << YAML::Key << "orientation";
859  std::vector<double> orientation =
860  {acummulated_poses_.goals[i].pose.orientation.w, acummulated_poses_.goals[i].pose.orientation.x,
861  acummulated_poses_.goals[i].pose.orientation.y,
862  acummulated_poses_.goals[i].pose.orientation.z};
863  out << YAML::Value << orientation;
864  out << YAML::EndMap;
865  }
866 
867  // open dialog to save it in a file
868  QString file = QFileDialog::getSaveFileName(
869  this,
870  tr("Open File"), "",
871  tr("yaml(*.yaml);;All Files (*)"));
872 
873  if (!file.toStdString().empty()) {
874  std::ofstream fout(file.toStdString() + ".yaml");
875  fout << out.c_str();
876  std::cout << "Saving waypoints succeeded" << std::endl;
877  } else {
878  std::cout << "Saving waypoints aborted" << std::endl;
879  }
880 }
881 
882 void
883 Nav2Panel::onInitialize()
884 {
885  node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
886  if (node_ptr_ == nullptr) {
887  // The node no longer exists, so just don't initialize
888  RCLCPP_ERROR(
889  rclcpp::get_logger("nav2_panel"),
890  "Underlying ROS node no longer exists, initialization failed");
891  return;
892  }
893  rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node(); // nosemgrep
894 
895  // declaring parameter to get the base frame
896  node->declare_parameter("base_frame", rclcpp::ParameterValue(std::string("base_footprint")));
897  node->get_parameter("base_frame", base_frame_);
898 
899  // create action feedback subscribers
900  navigation_feedback_sub_ =
901  node->create_subscription<nav2_msgs::action::NavigateToPose::Impl::FeedbackMessage>(
902  "navigate_to_pose/_action/feedback",
903  rclcpp::SystemDefaultsQoS(),
904  [this](const nav2_msgs::action::NavigateToPose::Impl::FeedbackMessage::ConstSharedPtr & msg) {
905  if (stoi(nr_of_loops_->displayText().toStdString()) > 0) {
906  if (goal_index_ == 0 && !loop_counter_stop_) {
907  loop_count_++;
908  loop_counter_stop_ = true;
909  }
910  if (goal_index_ != 0) {
911  loop_counter_stop_ = false;
912  }
913  navigation_feedback_indicator_->setText(
914  getNavToPoseFeedbackLabel(msg->feedback) + QString(
915  std::string(
916  "</td></tr><tr><td width=150>Waypoint:</td><td>" +
917  toString(goal_index_ + 1)).c_str()) + QString(
918  std::string(
919  "</td></tr><tr><td width=150>Loop:</td><td>" +
920  toString(loop_count_)).c_str()));
921  } else {
922  navigation_feedback_indicator_->setText(getNavToPoseFeedbackLabel(msg->feedback));
923  }
924  });
925  nav_through_poses_feedback_sub_ =
926  node->create_subscription<nav2_msgs::action::NavigateThroughPoses::Impl::FeedbackMessage>(
927  "navigate_through_poses/_action/feedback",
928  rclcpp::SystemDefaultsQoS(),
929  [this](const nav2_msgs::action::NavigateThroughPoses::Impl::FeedbackMessage::ConstSharedPtr &
930  msg) {
931  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel(msg->feedback));
932  });
933 
934  // create action goal status subscribers
935  navigation_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
936  "navigate_to_pose/_action/status",
937  rclcpp::SystemDefaultsQoS(),
938  [this](const action_msgs::msg::GoalStatusArray::ConstSharedPtr & msg) {
939  navigation_goal_status_indicator_->setText(
940  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
941  // Clearing all the stored values once reaching the final goal
942  if (
943  loop_count_ == stoi(nr_of_loops_->displayText().toStdString()) &&
944  goal_index_ == static_cast<int>(store_poses_.goals.size()) - 1 &&
945  msg->status_list.back().status == action_msgs::msg::GoalStatus::STATUS_SUCCEEDED)
946  {
947  store_poses_ = nav_msgs::msg::Goals();
948  waypoint_status_indicator_->clear();
949  loop_no_ = "0";
950  loop_count_ = 0;
951  navigation_feedback_indicator_->setText(getNavToPoseFeedbackLabel());
952  }
953  });
954  nav_through_poses_goal_status_sub_ = node->create_subscription<action_msgs::msg::GoalStatusArray>(
955  "navigate_through_poses/_action/status",
956  rclcpp::SystemDefaultsQoS(),
957  [this](const action_msgs::msg::GoalStatusArray::ConstSharedPtr & msg) {
958  navigation_goal_status_indicator_->setText(
959  nav2_rviz_plugins::getGoalStatusLabel("Feedback", msg->status_list.back().status));
960  if (msg->status_list.back().status != action_msgs::msg::GoalStatus::STATUS_EXECUTING) {
961  navigation_feedback_indicator_->setText(getNavThroughPosesFeedbackLabel());
962  }
963  });
964 }
965 
966 void
967 Nav2Panel::startThread()
968 {
969  // start initial thread now that state machine is started
970  initial_thread_->start();
971 }
972 
973 void
974 Nav2Panel::onPause()
975 {
976  QFuture<bool> futureNav =
977  QtConcurrent::run(
978  std::bind(
980  client_nav_.get(), std::placeholders::_1), server_timeout_);
981  QFuture<bool> futureLoc =
982  QtConcurrent::run(
983  std::bind(
985  client_loc_.get(), std::placeholders::_1), server_timeout_);
986 }
987 
988 void
989 Nav2Panel::onResume()
990 {
991  QFuture<bool> futureNav =
992  QtConcurrent::run(
993  std::bind(
995  client_nav_.get(), std::placeholders::_1), server_timeout_);
996  QFuture<bool> futureLoc =
997  QtConcurrent::run(
998  std::bind(
1000  client_loc_.get(), std::placeholders::_1), server_timeout_);
1001 }
1002 
1003 void
1004 Nav2Panel::onIdle()
1005 {
1006  tools_tab_widget_->setTabEnabled(0, true);
1007  tools_tab_widget_->setTabEnabled(1, false);
1008  tools_tab_widget_->setTabEnabled(2, false);
1009  tools_tab_widget_->setCurrentIndex(0);
1010 }
1011 
1012 void
1013 Nav2Panel::onStartup()
1014 {
1015  QFuture<bool> futureNav =
1016  QtConcurrent::run(
1017  std::bind(
1019  client_nav_.get(), std::placeholders::_1), server_timeout_);
1020  QFuture<bool> futureLoc =
1021  QtConcurrent::run(
1022  std::bind(
1024  client_loc_.get(), std::placeholders::_1), server_timeout_);
1025 }
1026 
1027 void
1028 Nav2Panel::onShutdown()
1029 {
1030  QFuture<bool> futureNav =
1031  QtConcurrent::run(
1032  std::bind(
1034  client_nav_.get(), std::placeholders::_1), server_timeout_);
1035  QFuture<bool> futureLoc =
1036  QtConcurrent::run(
1037  std::bind(
1039  client_loc_.get(), std::placeholders::_1), server_timeout_);
1040  timer_.stop();
1041 }
1042 
1043 void
1044 Nav2Panel::onCancel()
1045 {
1046  QFuture<void> future =
1047  QtConcurrent::run(
1048  std::bind(
1049  &Nav2Panel::onCancelButtonPressed,
1050  this));
1051  waypoint_status_indicator_->clear();
1052  store_poses_ = nav_msgs::msg::Goals();
1053  acummulated_poses_ = nav_msgs::msg::Goals();
1054 }
1055 
1056 void Nav2Panel::onResumedWp()
1057 {
1058  QFuture<void> future =
1059  QtConcurrent::run(
1060  std::bind(
1061  &Nav2Panel::onCancelButtonPressed,
1062  this));
1063  acummulated_poses_ = store_poses_;
1064  loop_no_ = std::to_string(
1065  stoi(nr_of_loops_->displayText().toStdString()) -
1066  loop_count_);
1067  waypoint_status_indicator_->setText(
1068  QString(std::string("<b> Note: </b> Navigation is paused.").c_str()));
1069 }
1070 
1071 void
1072 Nav2Panel::onNewGoal(double x, double y, double theta, QString frame)
1073 {
1074  auto pose = geometry_msgs::msg::PoseStamped();
1075 
1076  // pose.header.stamp = client_node_->now(); // client node doesn't respect sim time yet
1077  pose.header.frame_id = frame.toStdString();
1078  pose.pose.position.x = x;
1079  pose.pose.position.y = y;
1080  pose.pose.position.z = 0.0;
1081  pose.pose.orientation = orientationAroundZAxis(theta);
1082 
1083  if (store_poses_.goals.empty()) {
1084  if (state_machine_.configuration().contains(accumulating_)) {
1085  waypoint_status_indicator_->clear();
1086  acummulated_poses_.goals.push_back(pose);
1087  syncTabsWithAccumulatedPoses(); // Sync tabs with new pose
1088  } else {
1089  acummulated_poses_ = nav_msgs::msg::Goals();
1090  updateWpNavigationMarkers();
1091  std::cout << "Start navigation" << std::endl;
1092  startNavigation(pose);
1093  }
1094  } else {
1095  waypoint_status_indicator_->setText(
1096  QString(std::string("<b> Note: </b> Cannot set goal in pause state").c_str()));
1097  }
1098  updateWpNavigationMarkers();
1099 }
1100 
1101 void
1102 Nav2Panel::onCancelButtonPressed()
1103 {
1104  if (navigation_goal_handle_) {
1105  auto future_cancel = navigation_action_client_->async_cancel_goal(navigation_goal_handle_);
1106 
1107  if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
1108  rclcpp::FutureReturnCode::SUCCESS)
1109  {
1110  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel goal");
1111  } else {
1112  navigation_goal_handle_.reset();
1113  }
1114  }
1115 
1116  if (waypoint_follower_goal_handle_) {
1117  auto future_cancel =
1118  waypoint_follower_action_client_->async_cancel_goal(waypoint_follower_goal_handle_);
1119 
1120  if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
1121  rclcpp::FutureReturnCode::SUCCESS)
1122  {
1123  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel waypoint follower");
1124  } else {
1125  waypoint_follower_goal_handle_.reset();
1126  }
1127  }
1128 
1129  if (nav_through_poses_goal_handle_) {
1130  auto future_cancel =
1131  nav_through_poses_action_client_->async_cancel_goal(nav_through_poses_goal_handle_);
1132 
1133  if (executor_->spin_until_future_complete(future_cancel, server_timeout_) !=
1134  rclcpp::FutureReturnCode::SUCCESS)
1135  {
1136  RCLCPP_ERROR(client_node_->get_logger(), "Failed to cancel nav through pose action");
1137  } else {
1138  nav_through_poses_goal_handle_.reset();
1139  }
1140  }
1141 
1142  timer_.stop();
1143 }
1144 
1145 void
1146 Nav2Panel::onAccumulatedWp()
1147 {
1148  updateAccumulatedPosesFromTabs();
1149 
1150  if (acummulated_poses_.goals.empty()) {
1151  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1152  waypoint_status_indicator_->setText(
1153  "<b> Note: </b> Uh oh! Someone forgot to select the waypoints");
1154  return;
1155  }
1156 
1157  // Sanity check for the loop value
1158  if (!isLoopValueValid(loop_no_)) {
1159  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1160  return;
1161  }
1162 
1163  // Remove any warning status at this point
1164  waypoint_status_indicator_->clear();
1165 
1166  // Disable navigation modes
1167  navigation_mode_button_->setEnabled(false);
1168  pause_resume_button_->setEnabled(false);
1169 
1172  if (store_poses_.goals.empty()) {
1173  std::cout << "Start waypoint" << std::endl;
1174 
1175  // Setting the final loop value on the text box for sanity
1176  nr_of_loops_->setText(QString::fromStdString(loop_no_));
1177 
1178  // Variable to store initial pose
1179  geometry_msgs::msg::TransformStamped init_transform;
1180 
1181  // Looking up transform to get initial pose
1182  if (store_initial_pose_) {
1183  try {
1184  init_transform = tf2_buffer_->lookupTransform(
1185  acummulated_poses_.goals[0].header.frame_id, base_frame_,
1186  tf2::TimePointZero);
1187  } catch (const tf2::TransformException & ex) {
1188  RCLCPP_INFO(
1189  client_node_->get_logger(), "Could not transform %s to %s: %s",
1190  acummulated_poses_.goals[0].header.frame_id.c_str(), base_frame_.c_str(), ex.what());
1191  return;
1192  }
1193 
1194  // Converting TransformStamped to PoseStamped
1195  geometry_msgs::msg::PoseStamped initial_pose;
1196  initial_pose.header = init_transform.header;
1197  initial_pose.pose.position.x = init_transform.transform.translation.x;
1198  initial_pose.pose.position.y = init_transform.transform.translation.y;
1199  initial_pose.pose.position.z = init_transform.transform.translation.z;
1200  initial_pose.pose.orientation.x = init_transform.transform.rotation.x;
1201  initial_pose.pose.orientation.y = init_transform.transform.rotation.y;
1202  initial_pose.pose.orientation.z = init_transform.transform.rotation.z;
1203  initial_pose.pose.orientation.w = init_transform.transform.rotation.w;
1204 
1205  // inserting the acummulated pose
1206  acummulated_poses_.goals.insert(acummulated_poses_.goals.begin(), initial_pose);
1207  syncTabsWithAccumulatedPoses();
1208  updateWpNavigationMarkers();
1209  initial_pose_stored_ = true;
1210  if (loop_count_ == 0) {
1211  goal_index_ = 1;
1212  }
1213  } else if (!store_initial_pose_ && initial_pose_stored_) {
1214  acummulated_poses_.goals.erase(
1215  acummulated_poses_.goals.begin(),
1216  acummulated_poses_.goals.begin());
1217  }
1218  } else {
1219  std::cout << "Resuming waypoint" << std::endl;
1220  }
1221 
1222  startWaypointFollowing(acummulated_poses_.goals);
1223  store_poses_ = acummulated_poses_;
1224 }
1225 
1226 void
1227 Nav2Panel::onAccumulatedNTP()
1228 {
1229  updateAccumulatedPosesFromTabs();
1230 
1231  std::cout << "Start navigate through poses" << std::endl;
1232  startNavThroughPoses(acummulated_poses_);
1233 }
1234 
1235 void
1236 Nav2Panel::onAccumulating()
1237 {
1238  acummulated_poses_ = nav_msgs::msg::Goals();
1239  store_poses_ = nav_msgs::msg::Goals();
1240  loop_count_ = 0;
1241  loop_no_ = "0";
1242  initial_pose_stored_ = false;
1243  loop_counter_stop_ = true;
1244  goal_index_ = 0;
1245  updateWpNavigationMarkers();
1246  syncTabsWithAccumulatedPoses();
1247 
1248  nav_to_pose_frame_id_->setText("map");
1249  nav_to_pose_x_->setValue(0.0);
1250  nav_to_pose_y_->setValue(0.0);
1251  nav_to_pose_yaw_->setValue(0.0);
1252 
1253  tools_tab_widget_->setTabEnabled(0, false);
1254  tools_tab_widget_->setTabEnabled(1, true);
1255  tools_tab_widget_->setCurrentIndex(1);
1256 }
1257 void
1258 Nav2Panel::timerEvent(QTimerEvent * event)
1259 {
1260  if (state_machine_.configuration().contains(accumulated_wp_)) {
1261  if (event->timerId() == timer_.timerId()) {
1262  if (!waypoint_follower_goal_handle_) {
1263  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1264  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1265  return;
1266  }
1267 
1268  executor_->spin_some();
1269  auto status = waypoint_follower_goal_handle_->get_status();
1270 
1271  // Check if the goal is still executing
1272  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1273  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1274  {
1275  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1276  } else {
1277  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1278  acummulated_poses_ = nav_msgs::msg::Goals();
1279  updateWpNavigationMarkers();
1280  timer_.stop();
1281  }
1282  }
1283  } else if (state_machine_.configuration().contains(accumulated_nav_through_poses_)) {
1284  if (event->timerId() == timer_.timerId()) {
1285  if (!nav_through_poses_goal_handle_) {
1286  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1287  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1288  return;
1289  }
1290 
1291  executor_->spin_some();
1292  auto status = nav_through_poses_goal_handle_->get_status();
1293 
1294  // Check if the goal is still executing
1295  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1296  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1297  {
1298  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1299  } else {
1300  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1301  acummulated_poses_ = nav_msgs::msg::Goals();
1302  updateWpNavigationMarkers();
1303  timer_.stop();
1304  }
1305  }
1306  } else {
1307  if (event->timerId() == timer_.timerId()) {
1308  if (!navigation_goal_handle_) {
1309  RCLCPP_DEBUG(client_node_->get_logger(), "Waiting for Goal");
1310  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1311  return;
1312  }
1313 
1314  executor_->spin_some();
1315  auto status = navigation_goal_handle_->get_status();
1316 
1317  // Check if the goal is still executing
1318  if (status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
1319  status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)
1320  {
1321  state_machine_.postEvent(new ROSActionQEvent(QActionState::ACTIVE));
1322  } else {
1323  state_machine_.postEvent(new ROSActionQEvent(QActionState::INACTIVE));
1324  nav_to_pose_frame_id_->setText("map");
1325  nav_to_pose_x_->setValue(0.0);
1326  nav_to_pose_y_->setValue(0.0);
1327  nav_to_pose_yaw_->setValue(0.0);
1328  timer_.stop();
1329  }
1330  }
1331  }
1332 }
1333 
1334 void
1335 Nav2Panel::startWaypointFollowing(std::vector<geometry_msgs::msg::PoseStamped> poses)
1336 {
1337  auto is_action_server_ready =
1338  waypoint_follower_action_client_->wait_for_action_server(std::chrono::seconds(5));
1339  if (!is_action_server_ready) {
1340  RCLCPP_ERROR(
1341  client_node_->get_logger(), "follow_waypoints action server is not available."
1342  " Is the initial pose set?");
1343  return;
1344  }
1345 
1346  // Send the goal poses
1347  waypoint_follower_goal_.poses = poses;
1348  waypoint_follower_goal_.goal_index = goal_index_;
1349  waypoint_follower_goal_.number_of_loops = stoi(loop_no_);
1350 
1351  RCLCPP_DEBUG(
1352  client_node_->get_logger(), "Sending a path of %zu waypoints:",
1353  waypoint_follower_goal_.poses.size());
1354  for (auto waypoint : waypoint_follower_goal_.poses) {
1355  RCLCPP_DEBUG(
1356  client_node_->get_logger(),
1357  "\t(%lf, %lf)", waypoint.pose.position.x, waypoint.pose.position.y);
1358  }
1359 
1360  // Enable result awareness by providing an empty lambda function
1361  auto send_goal_options =
1362  nav2::ActionClient<nav2_msgs::action::FollowWaypoints>::SendGoalOptions();
1363  send_goal_options.result_callback = [this](auto) {
1364  waypoint_follower_goal_handle_.reset();
1365  };
1366 
1367  send_goal_options.feedback_callback = [this](
1368  WaypointFollowerGoalHandle::SharedPtr /*goal_handle*/,
1369  const std::shared_ptr<const nav2_msgs::action::FollowWaypoints::Feedback> feedback) {
1370  goal_index_ = feedback->current_waypoint;
1371  };
1372 
1373  auto future_goal_handle =
1374  waypoint_follower_action_client_->async_send_goal(waypoint_follower_goal_, send_goal_options);
1375  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
1376  rclcpp::FutureReturnCode::SUCCESS)
1377  {
1378  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1379  return;
1380  }
1381 
1382  // Get the goal handle and save so that we can check on completion in the timer callback
1383  waypoint_follower_goal_handle_ = future_goal_handle.get();
1384  if (!waypoint_follower_goal_handle_) {
1385  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1386  return;
1387  }
1388 
1389  timer_.start(200, this);
1390 }
1391 
1392 void
1393 Nav2Panel::startNavThroughPoses(nav_msgs::msg::Goals poses)
1394 {
1395  auto is_action_server_ready =
1396  nav_through_poses_action_client_->wait_for_action_server(std::chrono::seconds(5));
1397  if (!is_action_server_ready) {
1398  RCLCPP_ERROR(
1399  client_node_->get_logger(), "navigate_through_poses action server is not available."
1400  " Is the initial pose set?");
1401  return;
1402  }
1403 
1404  nav_through_poses_goal_.poses = poses;
1405  nav_through_poses_goal_.behavior_tree = behavior_tree_file_->text().toStdString();
1406 
1407  if (nav_through_poses_goal_.behavior_tree.empty()) {
1408  RCLCPP_INFO(
1409  client_node_->get_logger(),
1410  "NavigateThroughPoses will be called using the BT Navigator's default behavior tree.");
1411  } else {
1412  RCLCPP_INFO(
1413  client_node_->get_logger(),
1414  "NavigateThroughPoses will be called using behavior tree: %s",
1415  nav_through_poses_goal_.behavior_tree.c_str());
1416  }
1417 
1418  RCLCPP_DEBUG(
1419  client_node_->get_logger(), "Sending a path of %zu waypoints:",
1420  nav_through_poses_goal_.poses.goals.size());
1421  for (auto waypoint : nav_through_poses_goal_.poses.goals) {
1422  RCLCPP_DEBUG(
1423  client_node_->get_logger(),
1424  "\t(%lf, %lf)", waypoint.pose.position.x, waypoint.pose.position.y);
1425  }
1426 
1427  // Enable result awareness by providing an empty lambda function
1428  auto send_goal_options =
1429  nav2::ActionClient<nav2_msgs::action::NavigateThroughPoses>::SendGoalOptions();
1430  send_goal_options.result_callback = [this](auto) {
1431  nav_through_poses_goal_handle_.reset();
1432  };
1433 
1434  auto future_goal_handle =
1435  nav_through_poses_action_client_->async_send_goal(nav_through_poses_goal_, send_goal_options);
1436  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
1437  rclcpp::FutureReturnCode::SUCCESS)
1438  {
1439  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1440  return;
1441  }
1442 
1443  // Get the goal handle and save so that we can check on completion in the timer callback
1444  nav_through_poses_goal_handle_ = future_goal_handle.get();
1445  if (!nav_through_poses_goal_handle_) {
1446  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1447  return;
1448  }
1449 
1450  timer_.start(200, this);
1451 }
1452 
1453 void
1454 Nav2Panel::startNavigation(geometry_msgs::msg::PoseStamped pose)
1455 {
1456  auto is_action_server_ready =
1457  navigation_action_client_->wait_for_action_server(std::chrono::seconds(5));
1458  if (!is_action_server_ready) {
1459  RCLCPP_ERROR(
1460  client_node_->get_logger(),
1461  "navigate_to_pose action server is not available."
1462  " Is the initial pose set?");
1463  return;
1464  }
1465 
1466  // Send the goal pose
1467  navigation_goal_.pose = pose;
1468  navigation_goal_.behavior_tree = behavior_tree_file_->text().toStdString();
1469 
1470  if (navigation_goal_.behavior_tree.empty()) {
1471  RCLCPP_INFO(
1472  client_node_->get_logger(),
1473  "NavigateToPose will be called using the BT Navigator's default behavior tree.");
1474  } else {
1475  RCLCPP_INFO(
1476  client_node_->get_logger(),
1477  "NavigateToPose will be called using behavior tree: %s",
1478  navigation_goal_.behavior_tree.c_str());
1479  }
1480 
1481  // Enable result awareness by providing an empty lambda function
1482  auto send_goal_options =
1483  nav2::ActionClient<nav2_msgs::action::NavigateToPose>::SendGoalOptions();
1484  send_goal_options.result_callback = [this](auto) {
1485  navigation_goal_handle_.reset();
1486  };
1487 
1488  auto future_goal_handle =
1489  navigation_action_client_->async_send_goal(navigation_goal_, send_goal_options);
1490  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
1491  rclcpp::FutureReturnCode::SUCCESS)
1492  {
1493  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1494  return;
1495  }
1496 
1497  // Get the goal handle and save so that we can check on completion in the timer callback
1498  navigation_goal_handle_ = future_goal_handle.get();
1499  if (!navigation_goal_handle_) {
1500  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1501  return;
1502  }
1503 
1504  timer_.start(200, this);
1505 }
1506 
1507 void
1508 Nav2Panel::save(rviz_common::Config config) const
1509 {
1510  Panel::save(config);
1511 }
1512 
1513 void
1514 Nav2Panel::load(const rviz_common::Config & config)
1515 {
1516  Panel::load(config);
1517 }
1518 
1519 void
1520 Nav2Panel::resetUniqueId()
1521 {
1522  unique_id = 0;
1523 }
1524 
1525 int
1526 Nav2Panel::getUniqueId()
1527 {
1528  int temp_id = unique_id;
1529  unique_id += 1;
1530  return temp_id;
1531 }
1532 
1533 void
1534 Nav2Panel::updateWpNavigationMarkers()
1535 {
1536  resetUniqueId();
1537 
1538  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
1539 
1540  visualization_msgs::msg::Marker clear_all_marker;
1541  clear_all_marker.action = visualization_msgs::msg::Marker::DELETEALL;
1542  marker_array->markers.push_back(clear_all_marker);
1543 
1544  wp_navigation_markers_pub_->publish(std::move(marker_array));
1545 
1546  marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
1547 
1548  for (size_t i = 0; i < acummulated_poses_.goals.size(); i++) {
1549  // Draw a green arrow at the waypoint pose
1550  visualization_msgs::msg::Marker arrow_marker;
1551  arrow_marker.header = acummulated_poses_.goals[i].header;
1552  arrow_marker.id = getUniqueId();
1553  arrow_marker.type = visualization_msgs::msg::Marker::ARROW;
1554  arrow_marker.action = visualization_msgs::msg::Marker::ADD;
1555  arrow_marker.pose = acummulated_poses_.goals[i].pose;
1556  arrow_marker.scale.x = 0.3;
1557  arrow_marker.scale.y = 0.05;
1558  arrow_marker.scale.z = 0.02;
1559  arrow_marker.color.r = 0;
1560  arrow_marker.color.g = 255;
1561  arrow_marker.color.b = 0;
1562  arrow_marker.color.a = 1.0f;
1563  arrow_marker.lifetime = rclcpp::Duration(0s);
1564  arrow_marker.frame_locked = false;
1565  marker_array->markers.push_back(arrow_marker);
1566 
1567  // Draw a red circle at the waypoint pose
1568  visualization_msgs::msg::Marker circle_marker;
1569  circle_marker.header = acummulated_poses_.goals[i].header;
1570  circle_marker.id = getUniqueId();
1571  circle_marker.type = visualization_msgs::msg::Marker::SPHERE;
1572  circle_marker.action = visualization_msgs::msg::Marker::ADD;
1573  circle_marker.pose = acummulated_poses_.goals[i].pose;
1574  circle_marker.scale.x = 0.05;
1575  circle_marker.scale.y = 0.05;
1576  circle_marker.scale.z = 0.05;
1577  circle_marker.color.r = 255;
1578  circle_marker.color.g = 0;
1579  circle_marker.color.b = 0;
1580  circle_marker.color.a = 1.0f;
1581  circle_marker.lifetime = rclcpp::Duration(0s);
1582  circle_marker.frame_locked = false;
1583  marker_array->markers.push_back(circle_marker);
1584 
1585  // Draw the waypoint number
1586  visualization_msgs::msg::Marker marker_text;
1587  marker_text.header = acummulated_poses_.goals[i].header;
1588  marker_text.id = getUniqueId();
1589  marker_text.type = visualization_msgs::msg::Marker::TEXT_VIEW_FACING;
1590  marker_text.action = visualization_msgs::msg::Marker::ADD;
1591  marker_text.pose = acummulated_poses_.goals[i].pose;
1592  marker_text.pose.position.z += 0.2; // draw it on top of the waypoint
1593  marker_text.scale.x = 0.07;
1594  marker_text.scale.y = 0.07;
1595  marker_text.scale.z = 0.07;
1596  marker_text.color.r = 0;
1597  marker_text.color.g = 255;
1598  marker_text.color.b = 0;
1599  marker_text.color.a = 1.0f;
1600  marker_text.lifetime = rclcpp::Duration(0s);
1601  marker_text.frame_locked = false;
1602  marker_text.text = "wp_" + std::to_string(i + 1);
1603  marker_array->markers.push_back(marker_text);
1604  }
1605 
1606  wp_navigation_markers_pub_->publish(std::move(marker_array));
1607 }
1608 
1609 inline QString
1610 Nav2Panel::getNavToPoseFeedbackLabel(nav2_msgs::action::NavigateToPose::Feedback msg)
1611 {
1612  return QString(std::string("<table>" + toLabel(msg) + "</table>").c_str());
1613 }
1614 
1615 inline QString
1616 Nav2Panel::getNavThroughPosesFeedbackLabel(nav2_msgs::action::NavigateThroughPoses::Feedback msg)
1617 {
1618  return QString(
1619  std::string(
1620  "<table><tr><td width=150>Poses remaining:</td><td>" +
1621  std::to_string(msg.number_of_poses_remaining) +
1622  "</td></tr>" + toLabel(msg) + "</table>").c_str());
1623 }
1624 
1625 template<typename T>
1626 inline std::string Nav2Panel::toLabel(T & msg)
1627 {
1628  return std::string(
1629  "<tr><td width=150>ETA:</td><td>" +
1630  toString(rclcpp::Duration(msg.estimated_time_remaining).seconds(), 0) + " s"
1631  "</td></tr><tr><td width=150>Distance remaining:</td><td>" +
1632  toString(msg.distance_remaining, 2) + " m"
1633  "</td></tr><tr><td width=150>Position error:</td><td>" +
1634  toString(msg.position_tracking_error, 2) + " m"
1635  "</td></tr><tr><td width=150>Heading error:</td><td>" +
1636  toString(msg.heading_tracking_error, 2) + " rad"
1637  "</td></tr><tr><td width=150>Time taken:</td><td>" +
1638  toString(rclcpp::Duration(msg.navigation_time).seconds(), 0) + " s"
1639  "</td></tr><tr><td width=150>Recoveries:</td><td>" +
1640  std::to_string(msg.number_of_recoveries) +
1641  "</td></tr>");
1642 }
1643 
1644 inline std::string
1645 Nav2Panel::toString(double val, int precision)
1646 {
1647  std::ostringstream out;
1648  out.precision(precision);
1649  out << std::fixed << val;
1650  return out.str();
1651 }
1652 
1653 void
1654 Nav2Panel::onSendNavToPose()
1655 {
1656  auto pose = geometry_msgs::msg::PoseStamped();
1657  pose.header.frame_id = nav_to_pose_frame_id_->text().toStdString();
1658  pose.header.stamp = client_node_->now();
1659  pose.pose.position.x = nav_to_pose_x_->value();
1660  pose.pose.position.y = nav_to_pose_y_->value();
1661  pose.pose.position.z = 0.0;
1662  pose.pose.orientation = orientationAroundZAxis(nav_to_pose_yaw_->value());
1663 
1664  auto is_action_server_ready =
1665  navigation_action_client_->wait_for_action_server(std::chrono::seconds(5));
1666  if (!is_action_server_ready) {
1667  RCLCPP_ERROR(
1668  client_node_->get_logger(),
1669  "navigate_to_pose action server is not available.");
1670  return;
1671  }
1672 
1673  navigation_goal_.pose = pose;
1674  navigation_goal_.behavior_tree = behavior_tree_file_->text().toStdString();
1675 
1676  auto send_goal_options =
1677  nav2::ActionClient<nav2_msgs::action::NavigateToPose>::SendGoalOptions();
1678  send_goal_options.result_callback = [this](auto) {
1679  navigation_goal_handle_.reset();
1680  };
1681 
1682  auto future_goal_handle =
1683  navigation_action_client_->async_send_goal(navigation_goal_, send_goal_options);
1684  if (executor_->spin_until_future_complete(future_goal_handle, server_timeout_) !=
1685  rclcpp::FutureReturnCode::SUCCESS)
1686  {
1687  RCLCPP_ERROR(client_node_->get_logger(), "Send goal call failed");
1688  return;
1689  }
1690 
1691  navigation_goal_handle_ = future_goal_handle.get();
1692  if (!navigation_goal_handle_) {
1693  RCLCPP_ERROR(client_node_->get_logger(), "Goal was rejected by server");
1694  return;
1695  }
1696 
1697  timer_.start(200, this);
1698 }
1699 
1700 void
1701 Nav2Panel::onAddNavThroughPose()
1702 {
1703  int index = nav_through_pose_tabs_.size();
1704  createNavThroughPoseTab(index);
1705  updateAccumulatedPosesFromTabs();
1706 }
1707 
1708 void
1709 Nav2Panel::onRemoveNavThroughPose()
1710 {
1711  if (!nav_through_pose_tabs_.empty()) {
1712  int index = nav_through_poses_tabs_->currentIndex();
1713  if (index >= 0 && index < static_cast<int>(nav_through_pose_tabs_.size())) {
1714  nav_through_poses_tabs_->removeTab(index);
1715  nav_through_pose_tabs_.erase(nav_through_pose_tabs_.begin() + index);
1716 
1717  for (int i = 0; i < nav_through_poses_tabs_->count(); ++i) {
1718  nav_through_poses_tabs_->setTabText(i, QString("Pose %1").arg(i + 1));
1719  }
1720 
1721  updateAccumulatedPosesFromTabs();
1722  }
1723  }
1724 }
1725 
1726 void
1727 Nav2Panel::createNavThroughPoseTab(int index)
1728 {
1729  QWidget * tab_widget = new QWidget;
1730  QGridLayout * layout = new QGridLayout;
1731 
1732  NavThroughPoseTab tab;
1733 
1734  layout->addWidget(new QLabel("Frame ID:"), 0, 0);
1735  tab.frame_id_edit = new QLineEdit("map");
1736  QObject::connect(
1737  tab.frame_id_edit, &QLineEdit::textChanged, this,
1738  &Nav2Panel::updateAccumulatedPosesFromTabs);
1739  layout->addWidget(tab.frame_id_edit, 0, 1);
1740 
1741  layout->addWidget(new QLabel("Position X:"), 1, 0);
1742  tab.pos_x_spin = new QDoubleSpinBox;
1743  tab.pos_x_spin->setRange(-1000.0, 1000.0);
1744  tab.pos_x_spin->setDecimals(3);
1745  tab.pos_x_spin->setSingleStep(0.1);
1746  QObject::connect(
1747  tab.pos_x_spin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
1748  &Nav2Panel::updateAccumulatedPosesFromTabs);
1749  layout->addWidget(tab.pos_x_spin, 1, 1);
1750 
1751  layout->addWidget(new QLabel("Position Y:"), 2, 0);
1752  tab.pos_y_spin = new QDoubleSpinBox;
1753  tab.pos_y_spin->setRange(-1000.0, 1000.0);
1754  tab.pos_y_spin->setDecimals(3);
1755  tab.pos_y_spin->setSingleStep(0.1);
1756  QObject::connect(
1757  tab.pos_y_spin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
1758  &Nav2Panel::updateAccumulatedPosesFromTabs);
1759  layout->addWidget(tab.pos_y_spin, 2, 1);
1760 
1761  layout->addWidget(new QLabel("Yaw (radians):"), 3, 0);
1762  tab.yaw_spin = new QDoubleSpinBox;
1763  tab.yaw_spin->setRange(-M_PI, M_PI);
1764  tab.yaw_spin->setDecimals(4);
1765  tab.yaw_spin->setSingleStep(0.01);
1766  QObject::connect(
1767  tab.yaw_spin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
1768  &Nav2Panel::updateAccumulatedPosesFromTabs);
1769  layout->addWidget(tab.yaw_spin, 3, 1);
1770 
1771  tab_widget->setLayout(layout);
1772 
1773  nav_through_poses_tabs_->addTab(tab_widget, QString("Pose %1").arg(index + 1));
1774  nav_through_pose_tabs_.push_back(tab);
1775 }
1776 
1777 void
1778 Nav2Panel::syncTabsWithAccumulatedPoses()
1779 {
1780  while (nav_through_poses_tabs_->count() > 0) {
1781  nav_through_poses_tabs_->removeTab(0);
1782  }
1783  nav_through_pose_tabs_.clear();
1784 
1785  for (size_t i = 0; i < acummulated_poses_.goals.size(); ++i) {
1786  createNavThroughPoseTab(i);
1787 
1788  const auto & pose = acummulated_poses_.goals[i];
1789 
1790  nav_through_pose_tabs_[i].frame_id_edit->blockSignals(true);
1791  nav_through_pose_tabs_[i].pos_x_spin->blockSignals(true);
1792  nav_through_pose_tabs_[i].pos_y_spin->blockSignals(true);
1793  nav_through_pose_tabs_[i].yaw_spin->blockSignals(true);
1794 
1795  nav_through_pose_tabs_[i].frame_id_edit->setText(
1796  QString::fromStdString(pose.header.frame_id));
1797  nav_through_pose_tabs_[i].pos_x_spin->setValue(pose.pose.position.x);
1798  nav_through_pose_tabs_[i].pos_y_spin->setValue(pose.pose.position.y);
1799 
1800  tf2::Quaternion q(
1801  pose.pose.orientation.x,
1802  pose.pose.orientation.y,
1803  pose.pose.orientation.z,
1804  pose.pose.orientation.w);
1805  tf2::Matrix3x3 m(q);
1806  double roll, pitch, yaw;
1807  m.getRPY(roll, pitch, yaw);
1808  nav_through_pose_tabs_[i].yaw_spin->setValue(yaw);
1809 
1810  nav_through_pose_tabs_[i].frame_id_edit->blockSignals(false);
1811  nav_through_pose_tabs_[i].pos_x_spin->blockSignals(false);
1812  nav_through_pose_tabs_[i].pos_y_spin->blockSignals(false);
1813  nav_through_pose_tabs_[i].yaw_spin->blockSignals(false);
1814  }
1815 }
1816 
1817 void
1818 Nav2Panel::updateAccumulatedPosesFromTabs()
1819 {
1820  acummulated_poses_.goals.clear();
1821 
1822  for (const auto & tab : nav_through_pose_tabs_) {
1823  geometry_msgs::msg::PoseStamped pose;
1824  pose.header.frame_id = tab.frame_id_edit->text().toStdString();
1825  pose.header.stamp = client_node_->now();
1826  pose.pose.position.x = tab.pos_x_spin->value();
1827  pose.pose.position.y = tab.pos_y_spin->value();
1828  pose.pose.position.z = 0.0;
1829  pose.pose.orientation = orientationAroundZAxis(tab.yaw_spin->value());
1830  acummulated_poses_.goals.push_back(pose);
1831  }
1832 
1833  updateWpNavigationMarkers();
1834 }
1835 
1836 } // namespace nav2_rviz_plugins
1837 
1838 #include <pluginlib/class_list_macros.hpp> // NOLINT
1839 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