Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
robot_navigator.py
1 #! /usr/bin/env python3
2 # Copyright 2021 Samsung Research America
3 # Copyright 2025 Open Navigation LLC
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 
18 from enum import Enum
19 import time
20 from typing import Any, Union
21 
22 from action_msgs.msg import GoalStatus
23 from builtin_interfaces.msg import Duration
24 from geographic_msgs.msg import GeoPose
25 from geometry_msgs.msg import Point, PoseStamped, PoseWithCovarianceStamped
26 from lifecycle_msgs.srv import GetState
27 from nav2_msgs.action import (AssistedTeleop, BackUp, # type: ignore[attr-defined]
28  ComputeAndTrackRoute, ComputePathThroughPoses, ComputePathToPose,
29  ComputeRoute, DockRobot, DriveOnHeading, FollowGPSWaypoints,
30  FollowObject, FollowPath, FollowWaypoints, NavigateThroughPoses,
31  NavigateToPose, SmoothPath, Spin, UndockRobot)
32 from nav2_msgs.srv import (ClearCostmapAroundPose, ClearCostmapAroundRobot,
33  ClearCostmapExceptRegion, ClearEntireCostmap, GetCostmap, LoadMap,
34  ManageLifecycleNodes, Toggle)
35 from nav_msgs.msg import Goals, Path
36 import rclpy
37 from rclpy.action import ActionClient
38 from rclpy.client import Client
39 from rclpy.duration import Duration as rclpyDuration
40 from rclpy.node import Node
41 from rclpy.qos import QoSDurabilityPolicy, QoSHistoryPolicy, QoSProfile, QoSReliabilityPolicy
42 
43 
44 # Task Result enum for the result of the task being executed
45 class TaskResult(Enum):
46  UNKNOWN = 0
47  SUCCEEDED = 1
48  CANCELED = 2
49  FAILED = 3
50 
51 
52 # Task enum for the task being executed, if its a long-running task to be able to obtain
53 # necessary contextual information in `isTaskComplete` and `getFeedback` regarding the task
54 # which is running.
55 class RunningTask(Enum):
56  NONE = 0
57  NAVIGATE_TO_POSE = 1
58  NAVIGATE_THROUGH_POSES = 2
59  FOLLOW_PATH = 3
60  FOLLOW_WAYPOINTS = 4
61  FOLLOW_GPS_WAYPOINTS = 5
62  SPIN = 6
63  BACKUP = 7
64  DRIVE_ON_HEADING = 8
65  ASSISTED_TELEOP = 9
66  DOCK_ROBOT = 10
67  UNDOCK_ROBOT = 11
68  COMPUTE_AND_TRACK_ROUTE = 12
69  FOLLOW_OBJECT = 13
70 
71 
72 class BasicNavigator(Node):
73 
74  def __init__(self, node_name: str = 'basic_navigator', namespace: str = ''):
75  super().__init__(node_name=node_name, namespace=namespace)
76  self.initial_poseinitial_pose = PoseStamped()
77  self.initial_poseinitial_pose.header.frame_id = 'map'
78 
79  self.goal_handlegoal_handle = None
80  self.result_futureresult_future = None
81  self.feedbackfeedback = None
82  self.statusstatus = None
83 
84  # Since the route server's compute and track action server is likely
85  # to be running simultaneously with another (e.g. controller, WPF) server,
86  # we must track its futures and feedback separately. Additionally, the
87  # route tracking feedback is uniquely important to be complete and ordered
88  self.route_goal_handleroute_goal_handle = None
89  self.route_result_futureroute_result_future = None
90  self.route_feedbackroute_feedback = []
91 
92  # Error code and messages from servers
93  self.last_action_error_codelast_action_error_code = 0
94  self.last_action_error_msglast_action_error_msg = ''
95 
96  amcl_pose_qos = QoSProfile(
97  durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
98  reliability=QoSReliabilityPolicy.RELIABLE,
99  history=QoSHistoryPolicy.KEEP_LAST,
100  depth=1,
101  )
102 
103  self.initial_pose_receivedinitial_pose_received = False
104  self.nav_through_poses_clientnav_through_poses_client = ActionClient(
105  self, NavigateThroughPoses, 'navigate_through_poses')
106  self.nav_to_pose_clientnav_to_pose_client = ActionClient(self, NavigateToPose, 'navigate_to_pose')
107  self.follow_waypoints_clientfollow_waypoints_client = ActionClient(
108  self, FollowWaypoints, 'follow_waypoints'
109  )
110  self.follow_gps_waypoints_clientfollow_gps_waypoints_client = ActionClient(
111  self, FollowGPSWaypoints, 'follow_gps_waypoints'
112  )
113  self.follow_path_clientfollow_path_client = ActionClient(self, FollowPath, 'follow_path')
114  self.compute_path_to_pose_clientcompute_path_to_pose_client = ActionClient(
115  self, ComputePathToPose, 'compute_path_to_pose'
116  )
117  self.compute_path_through_poses_clientcompute_path_through_poses_client = ActionClient(
118  self, ComputePathThroughPoses, 'compute_path_through_poses'
119  )
120  self.smoother_clientsmoother_client = ActionClient(self, SmoothPath, 'smooth_path')
121  self.compute_route_clientcompute_route_client = ActionClient(self, ComputeRoute, 'compute_route')
122  self.compute_and_track_route_clientcompute_and_track_route_client = ActionClient(
123  self,
124  ComputeAndTrackRoute,
125  'compute_and_track_route',
126  )
127  self.spin_clientspin_client = ActionClient(self, Spin, 'spin')
128 
129  self.backup_clientbackup_client = ActionClient(self, BackUp, 'backup')
130  self.drive_on_heading_clientdrive_on_heading_client = ActionClient(
131  self, DriveOnHeading, 'drive_on_heading'
132  )
133  self.assisted_teleop_clientassisted_teleop_client = ActionClient(
134  self, AssistedTeleop, 'assisted_teleop'
135  )
136  self.docking_clientdocking_client = ActionClient(self, DockRobot, 'dock_robot')
137  self.undocking_clientundocking_client = ActionClient(self, UndockRobot, 'undock_robot')
138  self.following_clientfollowing_client = ActionClient(self, FollowObject, 'follow_object')
139 
140  self.localization_pose_sublocalization_pose_sub = self.create_subscription(
141  PoseWithCovarianceStamped,
142  'amcl_pose',
143  self._amclPoseCallback_amclPoseCallback,
144  amcl_pose_qos,
145  )
146  self.initial_pose_pubinitial_pose_pub = self.create_publisher(
147  PoseWithCovarianceStamped, 'initialpose', 10
148  )
149  self.change_maps_srvchange_maps_srv = \
150  self.create_client(LoadMap, 'map_server/load_map')
151  self.clear_costmap_global_srvclear_costmap_global_srv = self.create_client(
152  ClearEntireCostmap,
153  'global_costmap/clear_entirely_global_costmap',
154  )
155  self.clear_costmap_local_srvclear_costmap_local_srv = self.create_client(
156  ClearEntireCostmap,
157  'local_costmap/clear_entirely_local_costmap',
158  )
159  self.clear_costmap_except_region_srvclear_costmap_except_region_srv = self.create_client(
160  ClearCostmapExceptRegion,
161  'local_costmap/clear_costmap_except_region',
162  )
163  self.clear_costmap_around_robot_srvclear_costmap_around_robot_srv = self.create_client(
164  ClearCostmapAroundRobot,
165  'local_costmap/clear_costmap_around_robot',
166  )
167  self.clear_local_costmap_around_pose_srvclear_local_costmap_around_pose_srv = self.create_client(
168  ClearCostmapAroundPose,
169  'local_costmap/clear_costmap_around_pose',
170  )
171  self.clear_global_costmap_around_pose_srvclear_global_costmap_around_pose_srv = self.create_client(
172  ClearCostmapAroundPose,
173  'global_costmap/clear_costmap_around_pose',
174  )
175  self.get_costmap_global_srvget_costmap_global_srv = self.create_client(
176  GetCostmap,
177  'global_costmap/get_costmap',
178  )
179  self.get_costmap_local_srvget_costmap_local_srv = self.create_client(
180  GetCostmap,
181  'local_costmap/get_costmap',
182  )
183  self.toggle_collision_monitor_srvtoggle_collision_monitor_srv = self.create_client(
184  Toggle,
185  'collision_monitor/toggle',
186  )
187 
188  def destroyNode(self):
189  self.destroy_nodedestroy_node()
190 
191  def destroy_node(self):
192  self.nav_through_poses_clientnav_through_poses_client.destroy()
193  self.nav_to_pose_clientnav_to_pose_client.destroy()
194  self.follow_waypoints_clientfollow_waypoints_client.destroy()
195  self.follow_path_clientfollow_path_client.destroy()
196  self.compute_path_to_pose_clientcompute_path_to_pose_client.destroy()
197  self.compute_path_through_poses_clientcompute_path_through_poses_client.destroy()
198  self.compute_and_track_route_clientcompute_and_track_route_client.destroy()
199  self.compute_route_clientcompute_route_client.destroy()
200  self.smoother_clientsmoother_client.destroy()
201  self.spin_clientspin_client.destroy()
202  self.backup_clientbackup_client.destroy()
203  self.drive_on_heading_clientdrive_on_heading_client.destroy()
204  self.assisted_teleop_clientassisted_teleop_client.destroy()
205  self.follow_gps_waypoints_clientfollow_gps_waypoints_client.destroy()
206  self.docking_clientdocking_client.destroy()
207  self.undocking_clientundocking_client.destroy()
208  super().destroy_node()
209 
210  def setInitialPose(self, initial_pose: PoseStamped):
211  """Set the initial pose to the localization system."""
212  self.initial_pose_receivedinitial_pose_received = False
213  self.initial_poseinitial_pose = initial_pose
214  self._setInitialPose_setInitialPose()
215 
216  def goThroughPoses(self, poses: Goals, behavior_tree: str = ''):
217  """Send a `NavThroughPoses` action request."""
218  self.clearPreviousStateclearPreviousState()
219  self.debugdebug("Waiting for 'NavigateThroughPoses' action server")
220  while not self.nav_through_poses_clientnav_through_poses_client.wait_for_server(timeout_sec=1.0):
221  self.infoinfo("'NavigateThroughPoses' action server not available, waiting...")
222 
223  goal_msg = NavigateThroughPoses.Goal()
224  goal_msg.poses = poses
225  goal_msg.behavior_tree = behavior_tree
226 
227  self.infoinfo(f'Navigating with {len(poses.goals)} goals....')
228  send_goal_future = self.nav_through_poses_clientnav_through_poses_client.send_goal_async(
229  goal_msg, self._feedbackCallback_feedbackCallback
230  )
231  rclpy.spin_until_future_complete(self, send_goal_future)
232  self.goal_handlegoal_handle = send_goal_future.result()
233 
234  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
235  msg = f'NavigateThroughPoses request with {len(poses.goals)} was rejected!'
236  self.setTaskErrorsetTaskError(NavigateThroughPoses.Result.UNKNOWN, msg)
237  self.errorerror(msg)
238  return None
239 
240  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
241  return RunningTask.NAVIGATE_THROUGH_POSES
242 
243  def goToPose(self, pose: PoseStamped, behavior_tree: str = ''):
244  """Send a `NavToPose` action request."""
245  self.clearPreviousStateclearPreviousState()
246  self.debugdebug("Waiting for 'NavigateToPose' action server")
247  while not self.nav_to_pose_clientnav_to_pose_client.wait_for_server(timeout_sec=1.0):
248  self.infoinfo("'NavigateToPose' action server not available, waiting...")
249 
250  goal_msg = NavigateToPose.Goal()
251  goal_msg.pose = pose
252  goal_msg.behavior_tree = behavior_tree
253 
254  self.infoinfo(
255  'Navigating to goal: '
256  + str(pose.pose.position.x)
257  + ' '
258  + str(pose.pose.position.y)
259  + '...'
260  )
261  send_goal_future = self.nav_to_pose_clientnav_to_pose_client.send_goal_async(
262  goal_msg, self._feedbackCallback_feedbackCallback
263  )
264  rclpy.spin_until_future_complete(self, send_goal_future)
265  self.goal_handlegoal_handle = send_goal_future.result()
266 
267  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
268  msg = (
269  'NavigateToPose goal to '
270  + str(pose.pose.position.x)
271  + ' '
272  + str(pose.pose.position.y)
273  + ' was rejected!'
274  )
275  self.setTaskErrorsetTaskError(NavigateToPose.Result.UNKNOWN, msg)
276  self.errorerror(msg)
277  return None
278 
279  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
280  return RunningTask.NAVIGATE_TO_POSE
281 
283  self, poses: list[PoseStamped], number_of_loops: int = 0, goal_index: int = 0
284  ):
285  """Send a `FollowWaypoints` action request."""
286  self.clearPreviousStateclearPreviousState()
287  self.debugdebug("Waiting for 'FollowWaypoints' action server")
288  while not self.follow_waypoints_clientfollow_waypoints_client.wait_for_server(timeout_sec=1.0):
289  self.infoinfo("'FollowWaypoints' action server not available, waiting...")
290 
291  goal_msg = FollowWaypoints.Goal()
292  goal_msg.poses = poses
293  goal_msg.number_of_loops = number_of_loops
294  goal_msg.goal_index = goal_index
295 
296  self.infoinfo(f'Following {len(goal_msg.poses)} goals....')
297  send_goal_future = self.follow_waypoints_clientfollow_waypoints_client.send_goal_async(
298  goal_msg, self._feedbackCallback_feedbackCallback
299  )
300  rclpy.spin_until_future_complete(self, send_goal_future)
301  self.goal_handlegoal_handle = send_goal_future.result()
302 
303  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
304  msg = f'Following {len(poses)} waypoints request was rejected!'
305  self.setTaskErrorsetTaskError(FollowWaypoints.Result.UNKNOWN, msg)
306  self.errorerror(msg)
307  return None
308 
309  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
310  return RunningTask.FOLLOW_WAYPOINTS
311 
312  def followGpsWaypoints(self, gps_poses: list[GeoPose]):
313  """Send a `FollowGPSWaypoints` action request."""
314  self.clearPreviousStateclearPreviousState()
315  self.debugdebug("Waiting for 'FollowWaypoints' action server")
316  while not self.follow_gps_waypoints_clientfollow_gps_waypoints_client.wait_for_server(timeout_sec=1.0):
317  self.infoinfo("'FollowWaypoints' action server not available, waiting...")
318 
319  goal_msg = FollowGPSWaypoints.Goal()
320  goal_msg.gps_poses = gps_poses
321 
322  self.infoinfo(f'Following {len(goal_msg.gps_poses)} gps goals....')
323  send_goal_future = self.follow_gps_waypoints_clientfollow_gps_waypoints_client.send_goal_async(
324  goal_msg, self._feedbackCallback_feedbackCallback
325  )
326  rclpy.spin_until_future_complete(self, send_goal_future)
327  self.goal_handlegoal_handle = send_goal_future.result()
328 
329  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
330  msg = f'Following {len(gps_poses)} gps waypoints request was rejected!'
331  self.setTaskErrorsetTaskError(FollowGPSWaypoints.Result.UNKNOWN, msg)
332  self.errorerror(msg)
333  return None
334 
335  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
336  return RunningTask.FOLLOW_GPS_WAYPOINTS
337 
338  def spin(
339  self, spin_dist: float = 1.57, time_allowance: int = 10,
340  disable_collision_checks: bool = False):
341  self.clearPreviousStateclearPreviousState()
342  self.debugdebug("Waiting for 'Spin' action server")
343  while not self.spin_clientspin_client.wait_for_server(timeout_sec=1.0):
344  self.infoinfo("'Spin' action server not available, waiting...")
345  goal_msg = Spin.Goal()
346  goal_msg.target_yaw = spin_dist
347  goal_msg.time_allowance = Duration(sec=time_allowance)
348  goal_msg.disable_collision_checks = disable_collision_checks
349 
350  self.infoinfo(f'Spinning to angle {goal_msg.target_yaw}....')
351  send_goal_future = self.spin_clientspin_client.send_goal_async(
352  goal_msg, self._feedbackCallback_feedbackCallback
353  )
354  rclpy.spin_until_future_complete(self, send_goal_future)
355  self.goal_handlegoal_handle = send_goal_future.result()
356 
357  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
358  msg = 'Spin request was rejected!'
359  self.setTaskErrorsetTaskError(Spin.Result.UNKNOWN, msg)
360  self.errorerror(msg)
361  return None
362 
363  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
364  return RunningTask.SPIN
365 
366  def backup(
367  self, backup_dist: float = 0.15, backup_speed: float = 0.025,
368  time_allowance: int = 10,
369  disable_collision_checks: bool = False):
370  self.clearPreviousStateclearPreviousState()
371  self.debugdebug("Waiting for 'Backup' action server")
372  while not self.backup_clientbackup_client.wait_for_server(timeout_sec=1.0):
373  self.infoinfo("'Backup' action server not available, waiting...")
374  goal_msg = BackUp.Goal()
375  goal_msg.target = Point(x=float(backup_dist))
376  goal_msg.speed = backup_speed
377  goal_msg.time_allowance = Duration(sec=time_allowance)
378  goal_msg.disable_collision_checks = disable_collision_checks
379 
380  self.infoinfo(f'Backing up {goal_msg.target.x} m at {goal_msg.speed} m/s....')
381  send_goal_future = self.backup_clientbackup_client.send_goal_async(
382  goal_msg, self._feedbackCallback_feedbackCallback
383  )
384  rclpy.spin_until_future_complete(self, send_goal_future)
385  self.goal_handlegoal_handle = send_goal_future.result()
386 
387  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
388  msg = 'Backup request was rejected!'
389  self.setTaskErrorsetTaskError(BackUp.Result.UNKNOWN, msg)
390  self.errorerror(msg)
391  return None
392 
393  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
394  return RunningTask.BACKUP
395 
396  def driveOnHeading(
397  self, dist: float = 0.15, speed: float = 0.025,
398  time_allowance: int = 10,
399  disable_collision_checks: bool = False):
400  self.clearPreviousStateclearPreviousState()
401  self.debugdebug("Waiting for 'DriveOnHeading' action server")
402  while not self.drive_on_heading_clientdrive_on_heading_client.wait_for_server(timeout_sec=1.0):
403  self.infoinfo("'DriveOnHeading' action server not available, waiting...")
404  goal_msg = DriveOnHeading.Goal()
405  goal_msg.target = Point(x=float(dist))
406  goal_msg.speed = speed
407  goal_msg.time_allowance = Duration(sec=time_allowance)
408  goal_msg.disable_collision_checks = disable_collision_checks
409 
410  self.infoinfo(f'Drive {goal_msg.target.x} m on heading at {goal_msg.speed} m/s....')
411  send_goal_future = self.drive_on_heading_clientdrive_on_heading_client.send_goal_async(
412  goal_msg, self._feedbackCallback_feedbackCallback
413  )
414  rclpy.spin_until_future_complete(self, send_goal_future)
415  self.goal_handlegoal_handle = send_goal_future.result()
416 
417  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
418  msg = 'Drive On Heading request was rejected!'
419  self.setTaskErrorsetTaskError(DriveOnHeading.Result.UNKNOWN, msg)
420  self.errorerror(msg)
421  return None
422 
423  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
424  return RunningTask.DRIVE_ON_HEADING
425 
426  def assistedTeleop(self, time_allowance: int = 30):
427 
428  self.clearPreviousStateclearPreviousState()
429  self.debugdebug("Wanting for 'assisted_teleop' action server")
430 
431  while not self.assisted_teleop_clientassisted_teleop_client.wait_for_server(timeout_sec=1.0):
432  self.infoinfo("'assisted_teleop' action server not available, waiting...")
433  goal_msg = AssistedTeleop.Goal()
434  goal_msg.time_allowance = Duration(sec=time_allowance)
435 
436  self.infoinfo("Running 'assisted_teleop'....")
437  send_goal_future = self.assisted_teleop_clientassisted_teleop_client.send_goal_async(
438  goal_msg, self._feedbackCallback_feedbackCallback
439  )
440  rclpy.spin_until_future_complete(self, send_goal_future)
441  self.goal_handlegoal_handle = send_goal_future.result()
442 
443  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
444  msg = 'Assisted Teleop request was rejected!'
445  self.setTaskErrorsetTaskError(AssistedTeleop.Result.UNKNOWN, msg)
446  self.errorerror(msg)
447  return None
448 
449  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
450  return RunningTask.ASSISTED_TELEOP
451 
452  def followPath(self, path: Path, controller_id: str = '',
453  goal_checker_id: str = '', progress_checker_id: str = '',
454  path_handler_id: str = ''):
455  self.clearPreviousStateclearPreviousState()
456  """Send a `FollowPath` action request."""
457  self.debugdebug("Waiting for 'FollowPath' action server")
458  while not self.follow_path_clientfollow_path_client.wait_for_server(timeout_sec=1.0):
459  self.infoinfo("'FollowPath' action server not available, waiting...")
460 
461  goal_msg = FollowPath.Goal()
462  goal_msg.path = path
463  goal_msg.controller_id = controller_id
464  goal_msg.goal_checker_id = goal_checker_id
465  goal_msg.progress_checker_id = progress_checker_id
466  goal_msg.path_handler_id = path_handler_id
467 
468  self.infoinfo('Executing path...')
469  send_goal_future = self.follow_path_clientfollow_path_client.send_goal_async(
470  goal_msg, self._feedbackCallback_feedbackCallback
471  )
472  rclpy.spin_until_future_complete(self, send_goal_future)
473  self.goal_handlegoal_handle = send_goal_future.result()
474 
475  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
476  msg = 'FollowPath goal was rejected!'
477  self.setTaskErrorsetTaskError(FollowPath.Result.UNKNOWN, msg)
478  self.errorerror(msg)
479  return None
480 
481  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
482  return RunningTask.FOLLOW_PATH
483 
484  def dockRobotByPose(self, dock_pose: PoseStamped,
485  dock_type: str = '', nav_to_dock: bool = True):
486  self.clearPreviousStateclearPreviousState()
487  """Send a `DockRobot` action request."""
488  self.infoinfo("Waiting for 'DockRobot' action server")
489  while not self.docking_clientdocking_client.wait_for_server(timeout_sec=1.0):
490  self.infoinfo('"DockRobot" action server not available, waiting...')
491 
492  goal_msg = DockRobot.Goal()
493  goal_msg.use_dock_id = False
494  goal_msg.dock_pose = dock_pose
495  goal_msg.dock_type = dock_type
496  goal_msg.navigate_to_staging_pose = nav_to_dock # if want to navigate before staging
497 
498  self.infoinfo('Docking at pose: ' + str(dock_pose) + '...')
499  send_goal_future = self.docking_clientdocking_client.send_goal_async(
500  goal_msg, self._feedbackCallback_feedbackCallback)
501  rclpy.spin_until_future_complete(self, send_goal_future)
502  self.goal_handlegoal_handle = send_goal_future.result()
503 
504  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
505  msg = 'DockRobot request was rejected!'
506  self.setTaskErrorsetTaskError(DockRobot.Result.UNKNOWN, msg)
507  self.errorerror(msg)
508  return None
509 
510  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
511  return RunningTask.DOCK_ROBOT
512 
513  def dockRobotByID(self, dock_id: str, nav_to_dock: bool = True):
514  """Send a `DockRobot` action request."""
515  self.clearPreviousStateclearPreviousState()
516  self.infoinfo("Waiting for 'DockRobot' action server")
517  while not self.docking_clientdocking_client.wait_for_server(timeout_sec=1.0):
518  self.infoinfo('"DockRobot" action server not available, waiting...')
519 
520  goal_msg = DockRobot.Goal()
521  goal_msg.use_dock_id = True
522  goal_msg.dock_id = dock_id
523  goal_msg.navigate_to_staging_pose = nav_to_dock # if want to navigate before staging
524 
525  self.infoinfo('Docking at dock ID: ' + str(dock_id) + '...')
526  send_goal_future = self.docking_clientdocking_client.send_goal_async(
527  goal_msg, self._feedbackCallback_feedbackCallback)
528  rclpy.spin_until_future_complete(self, send_goal_future)
529  self.goal_handlegoal_handle = send_goal_future.result()
530 
531  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
532  msg = 'DockRobot request was rejected!'
533  self.setTaskErrorsetTaskError(DockRobot.Result.UNKNOWN, msg)
534  self.errorerror(msg)
535  return None
536 
537  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
538  return RunningTask.DOCK_ROBOT
539 
540  def undockRobot(self, dock_type: str = ''):
541  """Send a `UndockRobot` action request."""
542  self.clearPreviousStateclearPreviousState()
543  self.infoinfo("Waiting for 'UndockRobot' action server")
544  while not self.undocking_clientundocking_client.wait_for_server(timeout_sec=1.0):
545  self.infoinfo('"UndockRobot" action server not available, waiting...')
546 
547  goal_msg = UndockRobot.Goal()
548  goal_msg.dock_type = dock_type
549 
550  self.infoinfo('Undocking from dock of type: ' + str(dock_type) + '...')
551  send_goal_future = self.undocking_clientundocking_client.send_goal_async(
552  goal_msg, self._feedbackCallback_feedbackCallback)
553  rclpy.spin_until_future_complete(self, send_goal_future)
554  self.goal_handlegoal_handle = send_goal_future.result()
555 
556  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
557  msg = 'UndockRobot request was rejected!'
558  self.setTaskErrorsetTaskError(UndockRobot.Result.UNKNOWN, msg)
559  self.errorerror(msg)
560  return None
561 
562  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
563  return RunningTask.UNDOCK_ROBOT
564 
565  def followObjectByTopic(self, topic: str, max_duration: int = 0):
566  """Send a `FollowObject` action request."""
567  self.clearPreviousStateclearPreviousState()
568  self.infoinfo("Waiting for 'FollowObject' action server")
569  while not self.following_clientfollowing_client.wait_for_server(timeout_sec=1.0):
570  self.infoinfo('"FollowObject" action server not available, waiting...')
571 
572  goal_msg = FollowObject.Goal()
573  goal_msg.pose_topic = topic
574  goal_msg.max_duration = Duration(sec=max_duration)
575 
576  self.infoinfo('Following object on topic: ' + str(topic) + '...')
577  send_goal_future = self.following_clientfollowing_client.send_goal_async(
578  goal_msg, self._feedbackCallback_feedbackCallback)
579  rclpy.spin_until_future_complete(self, send_goal_future)
580  self.goal_handlegoal_handle = send_goal_future.result()
581 
582  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
583  msg = 'FollowObject request was rejected!'
584  self.setTaskErrorsetTaskError(FollowObject.Result.UNKNOWN, msg)
585  self.errorerror(msg)
586  return None
587 
588  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
589  return RunningTask.FOLLOW_OBJECT
590 
591  def followObjectByFrame(self, frame: str, max_duration: int = 0):
592  """Send a `FollowObject` action request."""
593  self.clearPreviousStateclearPreviousState()
594  self.infoinfo("Waiting for 'FollowObject' action server")
595  while not self.following_clientfollowing_client.wait_for_server(timeout_sec=1.0):
596  self.infoinfo('"FollowObject" action server not available, waiting...')
597 
598  goal_msg = FollowObject.Goal()
599  goal_msg.tracked_frame = frame
600  goal_msg.max_duration = Duration(sec=max_duration)
601 
602  self.infoinfo('Following object in frame: ' + str(frame) + '...')
603  send_goal_future = self.following_clientfollowing_client.send_goal_async(
604  goal_msg, self._feedbackCallback_feedbackCallback)
605  rclpy.spin_until_future_complete(self, send_goal_future)
606  self.goal_handlegoal_handle = send_goal_future.result()
607 
608  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
609  msg = 'FollowObject request was rejected!'
610  self.setTaskErrorsetTaskError(FollowObject.Result.UNKNOWN, msg)
611  self.errorerror(msg)
612  return None
613 
614  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
615  return RunningTask.FOLLOW_OBJECT
616 
617  def cancelTask(self):
618  """Cancel pending task request of any type."""
619  self.infoinfo('Canceling current task.')
620  if self.result_futureresult_future:
621  if self.goal_handlegoal_handle is not None:
622  future = self.goal_handlegoal_handle.cancel_goal_async()
623  rclpy.spin_until_future_complete(self, future)
624  else:
625  self.errorerror('Cancel task failed, goal handle is None')
626  self.setTaskErrorsetTaskError(0, 'Cancel task failed, goal handle is None')
627  return
628  if self.route_result_futureroute_result_future:
629  if self.route_goal_handleroute_goal_handle is not None:
630  future = self.route_goal_handleroute_goal_handle.cancel_goal_async()
631  rclpy.spin_until_future_complete(self, future)
632  else:
633  self.errorerror('Cancel route task failed, goal handle is None')
634  self.setTaskErrorsetTaskError(0, 'Cancel route task failed, goal handle is None')
635  return
636  self.clearPreviousStateclearPreviousState()
637  return
638 
639  def isTaskComplete(self, task: RunningTask = RunningTask.NONE):
640  """Check if the task request of any type is complete yet."""
641  # Find the result future to spin
642  if task is None:
643  self.errorerror('Task is None, cannot check for completion')
644  return False
645 
646  result_future = None
647  if task != RunningTask.COMPUTE_AND_TRACK_ROUTE:
648  result_future = self.result_futureresult_future
649  else:
650  result_future = self.route_result_futureroute_result_future
651  if not result_future:
652  # task was cancelled or completed
653  return True
654 
655  # Get the result of the future, if complete
656  rclpy.spin_until_future_complete(self, result_future, timeout_sec=0.10)
657  result_response = result_future.result()
658 
659  if result_response:
660  self.statusstatus = result_response.status
661  if self.statusstatus != GoalStatus.STATUS_SUCCEEDED:
662  result = result_response.result
663  if result is not None:
664  self.setTaskErrorsetTaskError(result.error_code, result.error_msg)
665  self.debugdebug(
666  'Task with failed with'
667  f' status code:{self.status}'
668  f' error code:{result.error_code}'
669  f' error msg:{result.error_msg}')
670  return True
671  else:
672  self.setTaskErrorsetTaskError(0, 'No result received')
673  self.debugdebug('Task failed with no result received')
674  return True
675  else:
676  # Timed out, still processing, not complete yet
677  return False
678 
679  self.debugdebug('Task succeeded!')
680  return True
681 
682  def getFeedback(self, task: RunningTask = RunningTask.NONE):
683  """Get the pending action feedback message."""
684  if task != RunningTask.COMPUTE_AND_TRACK_ROUTE:
685  return self.feedbackfeedback
686  if len(self.route_feedbackroute_feedback) > 0:
687  return self.route_feedbackroute_feedback.pop(0)
688  return None
689 
690  def getResult(self):
691  """Get the pending action result message."""
692  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
693  return TaskResult.SUCCEEDED
694  elif self.statusstatus == GoalStatus.STATUS_ABORTED:
695  return TaskResult.FAILED
696  elif self.statusstatus == GoalStatus.STATUS_CANCELED:
697  return TaskResult.CANCELED
698  else:
699  return TaskResult.UNKNOWN
700 
701  def clearPreviousState(self):
702  self.feedbackfeedback = None
703  self.last_action_error_codelast_action_error_code = 0
704  self.last_action_error_msglast_action_error_msg = ''
705 
706  def setTaskError(self, error_code: int, error_msg: str):
707  self.last_action_error_codelast_action_error_code = error_code
708  self.last_action_error_msglast_action_error_msg = error_msg
709 
710  def getTaskError(self):
711  return (self.last_action_error_codelast_action_error_code, self.last_action_error_msglast_action_error_msg)
712 
713  def waitUntilNav2Active(self, navigator: str = 'bt_navigator',
714  localizer: str = 'amcl'):
715  """Block until the full navigation system is up and running."""
716  if localizer != 'robot_localization': # non-lifecycle node
717  self._waitForNodeToActivate_waitForNodeToActivate(localizer)
718  if localizer == 'amcl':
719  self._waitForInitialPose_waitForInitialPose()
720  self._waitForNodeToActivate_waitForNodeToActivate(navigator)
721  self.infoinfo('Nav2 is ready for use!')
722  return
723 
724  def _getPathImpl(
725  self, start: PoseStamped, goal: PoseStamped,
726  planner_id: str = '', use_start: bool = False
727  ):
728  """
729  Send a `ComputePathToPose` action request.
730 
731  Internal implementation to get the full result, not just the path.
732  """
733  self.debugdebug("Waiting for 'ComputePathToPose' action server")
734  while not self.compute_path_to_pose_clientcompute_path_to_pose_client.wait_for_server(timeout_sec=1.0):
735  self.infoinfo("'ComputePathToPose' action server not available, waiting...")
736 
737  goal_msg = ComputePathToPose.Goal()
738  goal_msg.start = start
739  goal_msg.goal = goal
740  goal_msg.planner_id = planner_id
741  goal_msg.use_start = use_start
742 
743  self.infoinfo('Getting path...')
744  send_goal_future = self.compute_path_to_pose_clientcompute_path_to_pose_client.send_goal_async(goal_msg)
745  rclpy.spin_until_future_complete(self, send_goal_future)
746  self.goal_handlegoal_handle = send_goal_future.result()
747 
748  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
749  self.errorerror('Get path was rejected!')
750  self.statusstatus = GoalStatus.STATUS_UNKNOWN
751  result = ComputePathToPose.Result()
752  result.error_code = ComputePathToPose.Result.UNKNOWN
753  result.error_msg = 'Get path was rejected'
754  return result
755 
756  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
757  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
758  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
759 
760  return self.result_futureresult_future.result().result # type: ignore[union-attr]
761 
762  def getPath(
763  self, start: PoseStamped, goal: PoseStamped,
764  planner_id: str = '', use_start: bool = False):
765  """Send a `ComputePathToPose` action request."""
766  self.clearPreviousStateclearPreviousState()
767  rtn = self._getPathImpl_getPathImpl(start, goal, planner_id, use_start)
768 
769  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
770  return rtn.path
771  else:
772  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
773  self.warnwarn('Getting path failed with'
774  f' status code:{self.status}'
775  f' error code:{rtn.error_code}'
776  f' error msg:{rtn.error_msg}')
777  return None
778 
779  def _getPathThroughPosesImpl(
780  self, start: PoseStamped, goals: list[PoseStamped],
781  planner_id: str = '', use_start: bool = False
782  ):
783  """
784  Send a `ComputePathThroughPoses` action request.
785 
786  Internal implementation to get the full result, not just the path.
787  """
788  self.debugdebug("Waiting for 'ComputePathThroughPoses' action server")
789  while not self.compute_path_through_poses_clientcompute_path_through_poses_client.wait_for_server(
790  timeout_sec=1.0
791  ):
792  self.infoinfo(
793  "'ComputePathThroughPoses' action server not available, waiting..."
794  )
795 
796  goal_msg = ComputePathThroughPoses.Goal()
797  goal_msg.start = start
798  goal_msg.goals.header.frame_id = 'map'
799  goal_msg.goals.header.stamp = self.get_clock().now().to_msg()
800  goal_msg.goals.goals = goals
801  goal_msg.planner_id = planner_id
802  goal_msg.use_start = use_start
803 
804  self.infoinfo('Getting path...')
805  send_goal_future = self.compute_path_through_poses_clientcompute_path_through_poses_client.send_goal_async(
806  goal_msg
807  )
808  rclpy.spin_until_future_complete(self, send_goal_future)
809  self.goal_handlegoal_handle = send_goal_future.result()
810 
811  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
812  self.errorerror('Get path was rejected!')
813  result = ComputePathThroughPoses.Result()
814  result.error_code = ComputePathThroughPoses.Result.UNKNOWN
815  result.error_msg = 'Get path was rejected!'
816  return result
817 
818  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
819  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
820  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
821 
822  return self.result_futureresult_future.result().result # type: ignore[union-attr]
823 
825  self, start: PoseStamped, goals: list[PoseStamped],
826  planner_id: str = '', use_start: bool = False):
827  """Send a `ComputePathThroughPoses` action request."""
828  self.clearPreviousStateclearPreviousState()
829  rtn = self._getPathThroughPosesImpl_getPathThroughPosesImpl(start, goals, planner_id, use_start)
830 
831  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
832  return rtn.path
833  else:
834  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
835  self.warnwarn('Getting path failed with'
836  f' status code:{self.status}'
837  f' error code:{rtn.error_code}'
838  f' error msg:{rtn.error_msg}')
839  return None
840 
841  def _getRouteImpl(
842  self, start: Union[int, PoseStamped],
843  goal: Union[int, PoseStamped], use_start: bool = False
844  ):
845  """
846  Send a `ComputeRoute` action request.
847 
848  Internal implementation to get the full result, not just the sparse route and dense path.
849  """
850  self.debugdebug("Waiting for 'ComputeRoute' action server")
851  while not self.compute_route_clientcompute_route_client.wait_for_server(timeout_sec=1.0):
852  self.infoinfo("'ComputeRoute' action server not available, waiting...")
853 
854  goal_msg = ComputeRoute.Goal()
855  goal_msg.use_start = use_start
856 
857  # Support both ID based requests and PoseStamped based requests
858  if isinstance(start, int) and isinstance(goal, int):
859  goal_msg.start_id = start
860  goal_msg.goal_id = goal
861  goal_msg.use_poses = False
862  elif isinstance(start, PoseStamped) and isinstance(goal, PoseStamped):
863  goal_msg.start = start
864  goal_msg.goal = goal
865  goal_msg.use_poses = True
866  else:
867  self.errorerror('Invalid start and goal types. Must be PoseStamped for pose or int for ID')
868  result = ComputeRoute.Result()
869  result.error_code = ComputeRoute.Result.UNKNOWN
870  result.error_msg = 'Request type fields were invalid!'
871  return result
872 
873  self.infoinfo('Getting route...')
874  send_goal_future = self.compute_route_clientcompute_route_client.send_goal_async(goal_msg)
875  rclpy.spin_until_future_complete(self, send_goal_future)
876  self.goal_handlegoal_handle = send_goal_future.result()
877 
878  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
879  self.errorerror('Get route was rejected!')
880  result = ComputeRoute.Result()
881  result.error_code = ComputeRoute.Result.UNKNOWN
882  result.error_msg = 'Get route was rejected!'
883  return result
884 
885  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
886  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
887  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
888 
889  return self.result_futureresult_future.result().result # type: ignore[union-attr]
890 
891  def getRoute(
892  self, start: Union[int, PoseStamped],
893  goal: Union[int, PoseStamped],
894  use_start: bool = False):
895  """Send a `ComputeRoute` action request."""
896  self.clearPreviousStateclearPreviousState()
897  rtn = self._getRouteImpl_getRouteImpl(start, goal, use_start=False)
898 
899  if self.statusstatus != GoalStatus.STATUS_SUCCEEDED:
900  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
901  self.warnwarn(
902  'Getting route failed with'
903  f' status code:{self.status}'
904  f' error code:{rtn.error_code}'
905  f' error msg:{rtn.error_msg}')
906  return None
907 
908  return [rtn.path, rtn.route]
909 
911  self, start: Union[int, PoseStamped],
912  goal: Union[int, PoseStamped], use_start: bool = False
913  ):
914  """Send a `ComputeAndTrackRoute` action request."""
915  self.clearPreviousStateclearPreviousState()
916  self.debugdebug("Waiting for 'ComputeAndTrackRoute' action server")
917  while not self.compute_and_track_route_clientcompute_and_track_route_client.wait_for_server(timeout_sec=1.0):
918  self.infoinfo("'ComputeAndTrackRoute' action server not available, waiting...")
919 
920  goal_msg = ComputeAndTrackRoute.Goal()
921  goal_msg.use_start = use_start
922 
923  # Support both ID based requests and PoseStamped based requests
924  if isinstance(start, int) and isinstance(goal, int):
925  goal_msg.start_id = start
926  goal_msg.goal_id = goal
927  goal_msg.use_poses = False
928  elif isinstance(start, PoseStamped) and isinstance(goal, PoseStamped):
929  goal_msg.start = start
930  goal_msg.goal = goal
931  goal_msg.use_poses = True
932  else:
933  self.setTaskErrorsetTaskError(ComputeAndTrackRoute.Result.UNKNOWN,
934  'Request type fields were invalid!')
935  self.errorerror('Invalid start and goal types. Must be PoseStamped for pose or int for ID')
936  return None
937 
938  self.infoinfo('Computing and tracking route...')
939  send_goal_future = self.compute_and_track_route_clientcompute_and_track_route_client.send_goal_async(goal_msg,
940  self._routeFeedbackCallback_routeFeedbackCallback) # noqa: E128
941  rclpy.spin_until_future_complete(self, send_goal_future)
942  self.route_goal_handleroute_goal_handle = send_goal_future.result()
943 
944  if not self.route_goal_handleroute_goal_handle or not self.route_goal_handleroute_goal_handle.accepted:
945  msg = 'Compute and track route was rejected!'
946  self.setTaskErrorsetTaskError(ComputeAndTrackRoute.Result.UNKNOWN, msg)
947  self.errorerror(msg)
948  return None
949 
950  self.route_result_futureroute_result_future = self.route_goal_handleroute_goal_handle.get_result_async()
951  return RunningTask.COMPUTE_AND_TRACK_ROUTE
952 
953  def _smoothPathImpl(
954  self, path: Path, smoother_id: str = '',
955  max_duration: float = 2.0, check_for_collision: bool = False
956  ):
957  """
958  Send a `SmoothPath` action request.
959 
960  Internal implementation to get the full result, not just the path.
961  """
962  self.debugdebug("Waiting for 'SmoothPath' action server")
963  while not self.smoother_clientsmoother_client.wait_for_server(timeout_sec=1.0):
964  self.infoinfo("'SmoothPath' action server not available, waiting...")
965 
966  goal_msg = SmoothPath.Goal()
967  goal_msg.path = path
968  goal_msg.max_smoothing_duration = rclpyDuration(seconds=max_duration).to_msg()
969  goal_msg.smoother_id = smoother_id
970  goal_msg.check_for_collisions = check_for_collision
971 
972  self.infoinfo('Smoothing path...')
973  send_goal_future = self.smoother_clientsmoother_client.send_goal_async(goal_msg)
974  rclpy.spin_until_future_complete(self, send_goal_future)
975  self.goal_handlegoal_handle = send_goal_future.result()
976 
977  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
978  self.errorerror('Smooth path was rejected!')
979  result = SmoothPath.Result()
980  result.error_code = SmoothPath.Result.UNKNOWN
981  result.error_msg = 'Smooth path was rejected'
982  return result
983 
984  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
985  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
986  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
987 
988  return self.result_futureresult_future.result().result # type: ignore[union-attr]
989 
991  self, path: Path, smoother_id: str = '',
992  max_duration: float = 2.0, check_for_collision: bool = False):
993  """Send a `SmoothPath` action request."""
994  self.clearPreviousStateclearPreviousState()
995  rtn = self._smoothPathImpl_smoothPathImpl(path, smoother_id, max_duration, check_for_collision)
996 
997  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
998  return rtn.path
999  else:
1000  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
1001  self.warnwarn('Getting path failed with'
1002  f' status code:{self.status}'
1003  f' error code:{rtn.error_code}'
1004  f' error msg:{rtn.error_msg}')
1005  return None
1006 
1007  def changeMap(self, map_filepath: str):
1008  """Change the current static map in the map server."""
1009  while not self.change_maps_srvchange_maps_srv.wait_for_service(timeout_sec=1.0):
1010  self.infoinfo('change map service not available, waiting...')
1011  req = LoadMap.Request()
1012  req.map_url = map_filepath
1013  future = self.change_maps_srvchange_maps_srv.call_async(req)
1014  rclpy.spin_until_future_complete(self, future)
1015 
1016  future_result = future.result()
1017  if future_result is None:
1018  self.errorerror('Change map request failed!')
1019  return False
1020 
1021  result = future_result.result
1022  if result != LoadMap.Response.RESULT_SUCCESS:
1023  if result == LoadMap.Response.RESULT_MAP_DOES_NOT_EXIST:
1024  reason = 'Map does not exist'
1025  elif result == LoadMap.Response.RESULT_INVALID_MAP_DATA:
1026  reason = 'Invalid map data'
1027  elif result == LoadMap.Response.RESULT_INVALID_MAP_METADATA:
1028  reason = 'Invalid map metadata'
1029  elif result == LoadMap.Response.RESULT_UNDEFINED_FAILURE:
1030  reason = 'Undefined failure'
1031  else:
1032  reason = 'Unknown'
1033  self.setTaskErrorsetTaskError(result, reason)
1034  self.errorerror(f'Change map request failed:{reason}!')
1035  return False
1036  else:
1037  self.infoinfo('Change map request was successful!')
1038  return True
1039 
1040  def clearAllCostmaps(self):
1041  """Clear all costmaps."""
1042  self.clearLocalCostmapclearLocalCostmap()
1043  self.clearGlobalCostmapclearGlobalCostmap()
1044  return
1045 
1047  """Clear local costmap."""
1048  while not self.clear_costmap_local_srvclear_costmap_local_srv.wait_for_service(timeout_sec=1.0):
1049  self.infoinfo('Clear local costmaps service not available, waiting...')
1050  req = ClearEntireCostmap.Request()
1051  future = self.clear_costmap_local_srvclear_costmap_local_srv.call_async(req)
1052  rclpy.spin_until_future_complete(self, future)
1053 
1054  result = future.result()
1055  if result is None:
1056  self.errorerror('Clear local costmap request failed!')
1057 
1058  return
1059 
1061  """Clear global costmap."""
1062  while not self.clear_costmap_global_srvclear_costmap_global_srv.wait_for_service(timeout_sec=1.0):
1063  self.infoinfo('Clear global costmaps service not available, waiting...')
1064  req = ClearEntireCostmap.Request()
1065  future = self.clear_costmap_global_srvclear_costmap_global_srv.call_async(req)
1066  rclpy.spin_until_future_complete(self, future)
1067 
1068  result = future.result()
1069  if result is None:
1070  self.errorerror('Clear global costmap request failed!')
1071 
1072  return
1073 
1074  def clearCostmapExceptRegion(self, reset_distance: float):
1075  """Clear the costmap except for a specified region."""
1076  while not self.clear_costmap_except_region_srvclear_costmap_except_region_srv.wait_for_service(timeout_sec=1.0):
1077  self.infoinfo('ClearCostmapExceptRegion service not available, waiting...')
1078  req = ClearCostmapExceptRegion.Request()
1079  req.reset_distance = reset_distance
1080  future = self.clear_costmap_except_region_srvclear_costmap_except_region_srv.call_async(req)
1081  rclpy.spin_until_future_complete(self, future)
1082 
1083  result = future.result()
1084  if result is None:
1085  self.errorerror('Clear costmap except region request failed!')
1086 
1087  return
1088 
1089  def clearCostmapAroundRobot(self, reset_distance: float):
1090  """Clear the costmap around the robot."""
1091  while not self.clear_costmap_around_robot_srvclear_costmap_around_robot_srv.wait_for_service(timeout_sec=1.0):
1092  self.infoinfo('ClearCostmapAroundRobot service not available, waiting...')
1093  req = ClearCostmapAroundRobot.Request()
1094  req.reset_distance = reset_distance
1095  future = self.clear_costmap_around_robot_srvclear_costmap_around_robot_srv.call_async(req)
1096  rclpy.spin_until_future_complete(self, future)
1097 
1098  result = future.result()
1099  if result is None:
1100  self.errorerror('Clear costmap around robot request failed!')
1101 
1102  return
1103 
1104  def clearLocalCostmapAroundPose(self, pose: PoseStamped, reset_distance: float):
1105  """Clear the costmap around a given pose."""
1106  while not self.clear_local_costmap_around_pose_srvclear_local_costmap_around_pose_srv.wait_for_service(timeout_sec=1.0):
1107  self.infoinfo('ClearLocalCostmapAroundPose service not available, waiting...')
1108  req = ClearCostmapAroundPose.Request()
1109  req.pose = pose
1110  req.reset_distance = reset_distance
1111  future = self.clear_local_costmap_around_pose_srvclear_local_costmap_around_pose_srv.call_async(req)
1112  rclpy.spin_until_future_complete(self, future)
1113 
1114  result = future.result()
1115  if result is None:
1116  self.errorerror('Clear local costmap around pose request failed!')
1117 
1118  return
1119 
1120  def clearGlobalCostmapAroundPose(self, pose: PoseStamped, reset_distance: float):
1121  """Clear the global costmap around a given pose."""
1122  while not self.clear_global_costmap_around_pose_srvclear_global_costmap_around_pose_srv.wait_for_service(timeout_sec=1.0):
1123  self.infoinfo('ClearGlobalCostmapAroundPose service not available, waiting...')
1124  req = ClearCostmapAroundPose.Request()
1125  req.pose = pose
1126  req.reset_distance = reset_distance
1127  future = self.clear_global_costmap_around_pose_srvclear_global_costmap_around_pose_srv.call_async(req)
1128  rclpy.spin_until_future_complete(self, future)
1129 
1130  result = future.result()
1131  if result is None:
1132  self.errorerror('Clear global costmap around pose request failed!')
1133 
1134  return
1135 
1136  def getGlobalCostmap(self):
1137  """Get the global costmap."""
1138  while not self.get_costmap_global_srvget_costmap_global_srv.wait_for_service(timeout_sec=1.0):
1139  self.infoinfo('Get global costmaps service not available, waiting...')
1140  req = GetCostmap.Request()
1141  future = self.get_costmap_global_srvget_costmap_global_srv.call_async(req)
1142  rclpy.spin_until_future_complete(self, future)
1143 
1144  result = future.result()
1145  if result is None:
1146  self.errorerror('Get global costmap request failed!')
1147  return None
1148 
1149  return result.map
1150 
1151  def getLocalCostmap(self):
1152  """Get the local costmap."""
1153  while not self.get_costmap_local_srvget_costmap_local_srv.wait_for_service(timeout_sec=1.0):
1154  self.infoinfo('Get local costmaps service not available, waiting...')
1155  req = GetCostmap.Request()
1156  future = self.get_costmap_local_srvget_costmap_local_srv.call_async(req)
1157  rclpy.spin_until_future_complete(self, future)
1158 
1159  result = future.result()
1160 
1161  if result is None:
1162  self.errorerror('Get local costmap request failed!')
1163  return None
1164 
1165  return result.map
1166 
1167  def toggleCollisionMonitor(self, enable: bool):
1168  """Toggle the collision monitor."""
1169  while not self.toggle_collision_monitor_srvtoggle_collision_monitor_srv.wait_for_service(timeout_sec=1.0):
1170  self.infoinfo('Toggle collision monitor service not available, waiting...')
1171  req = Toggle.Request()
1172  req.enable = enable
1173  future = self.toggle_collision_monitor_srvtoggle_collision_monitor_srv.call_async(req)
1174 
1175  rclpy.spin_until_future_complete(self, future)
1176  result = future.result()
1177  if result is None:
1178  self.errorerror('Toggle collision monitor request failed!')
1179 
1180  return
1181 
1182  def lifecycleStartup(self):
1183  """Startup nav2 lifecycle system."""
1184  self.infoinfo('Starting up lifecycle nodes based on lifecycle_manager.')
1185  for srv_name, srv_type in self.get_service_names_and_types():
1186  if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
1187  self.infoinfo(f'Starting up {srv_name}')
1188  mgr_client: Client[ManageLifecycleNodes.Request, ManageLifecycleNodes.Response] = \
1189  self.create_client(ManageLifecycleNodes, srv_name)
1190  while not mgr_client.wait_for_service(timeout_sec=1.0):
1191  self.infoinfo(f'{srv_name} service not available, waiting...')
1192  req = ManageLifecycleNodes.Request()
1193  req.command = ManageLifecycleNodes.Request.STARTUP
1194  future = mgr_client.call_async(req)
1195 
1196  # starting up requires a full map->odom->base_link TF tree
1197  # so if we're not successful, try forwarding the initial pose
1198  while True:
1199  rclpy.spin_until_future_complete(self, future, timeout_sec=0.10)
1200  if not future:
1201  self._waitForInitialPose_waitForInitialPose()
1202  else:
1203  break
1204  self.infoinfo('Nav2 is ready for use!')
1205  return
1206 
1208  """Shutdown nav2 lifecycle system."""
1209  self.infoinfo('Shutting down lifecycle nodes based on lifecycle_manager.')
1210  for srv_name, srv_type in self.get_service_names_and_types():
1211  if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
1212  self.infoinfo(f'Shutting down {srv_name}')
1213  mgr_client: Client[ManageLifecycleNodes.Request, ManageLifecycleNodes.Response] = \
1214  self.create_client(ManageLifecycleNodes, srv_name)
1215  while not mgr_client.wait_for_service(timeout_sec=1.0):
1216  self.infoinfo(f'{srv_name} service not available, waiting...')
1217  req = ManageLifecycleNodes.Request()
1218  req.command = ManageLifecycleNodes.Request.SHUTDOWN
1219  future = mgr_client.call_async(req)
1220  rclpy.spin_until_future_complete(self, future)
1221  future.result()
1222  return
1223 
1224  def _waitForNodeToActivate(self, node_name: str):
1225  # Waits for the node within the tester namespace to become active
1226  self.debugdebug(f'Waiting for {node_name} to become active..')
1227  node_service = f'{node_name}/get_state'
1228  state_client: Client[GetState.Request, GetState.Response] = \
1229  self.create_client(GetState, node_service)
1230  while not state_client.wait_for_service(timeout_sec=1.0):
1231  self.infoinfo(f'{node_service} service not available, waiting...')
1232 
1233  req = GetState.Request()
1234  state = 'unknown'
1235  while state != 'active':
1236  self.debugdebug(f'Getting {node_name} state...')
1237  future = state_client.call_async(req)
1238  rclpy.spin_until_future_complete(self, future)
1239 
1240  result = future.result()
1241  if result is not None:
1242  state = result.current_state.label
1243  self.debugdebug(f'Result of get_state: {state}')
1244  time.sleep(2)
1245  return
1246 
1247  def _waitForInitialPose(self):
1248  while not self.initial_pose_receivedinitial_pose_received:
1249  self.infoinfo('Setting initial pose')
1250  self._setInitialPose_setInitialPose()
1251  self.infoinfo('Waiting for amcl_pose to be received')
1252  rclpy.spin_once(self, timeout_sec=1.0)
1253  return
1254 
1255  def _amclPoseCallback(self, msg: PoseWithCovarianceStamped):
1256  self.debugdebug('Received amcl pose')
1257  self.initial_pose_receivedinitial_pose_received = True
1258  return
1259 
1260  def _feedbackCallback(self, msg: Any):
1261  self.debugdebug('Received action feedback message')
1262  self.feedbackfeedback = msg.feedback
1263  return
1264 
1265  def _routeFeedbackCallback(
1266  self, msg: ComputeAndTrackRoute.Impl.FeedbackMessage):
1267  self.debugdebug('Received route action feedback message')
1268  self.route_feedbackroute_feedback.append(msg.feedback)
1269  return
1270 
1271  def _setInitialPose(self):
1272  msg = PoseWithCovarianceStamped()
1273  msg.pose.pose = self.initial_poseinitial_pose.pose
1274  msg.header.frame_id = self.initial_poseinitial_pose.header.frame_id
1275  msg.header.stamp = self.initial_poseinitial_pose.header.stamp
1276  self.infoinfo('Publishing Initial Pose')
1277  self.initial_pose_pubinitial_pose_pub.publish(msg)
1278  return
1279 
1280  def info(self, msg: str):
1281  self.get_logger().info(msg)
1282  return
1283 
1284  def warn(self, msg: str):
1285  self.get_logger().warning(msg)
1286  return
1287 
1288  def error(self, msg: str):
1289  self.get_logger().error(msg)
1290  return
1291 
1292  def debug(self, msg: str):
1293  self.get_logger().debug(msg)
1294  return
def _getPathThroughPosesImpl(self, PoseStamped start, list[PoseStamped] goals, str planner_id='', bool use_start=False)
def goThroughPoses(self, Goals poses, str behavior_tree='')
def clearLocalCostmapAroundPose(self, PoseStamped pose, float reset_distance)
def getRoute(self, Union[int, PoseStamped] start, Union[int, PoseStamped] goal, bool use_start=False)
def followWaypoints(self, list[PoseStamped] poses, int number_of_loops=0, int goal_index=0)
def getPath(self, PoseStamped start, PoseStamped goal, str planner_id='', bool use_start=False)
def waitUntilNav2Active(self, str navigator='bt_navigator', str localizer='amcl')
def isTaskComplete(self, RunningTask task=RunningTask.NONE)
def _getPathImpl(self, PoseStamped start, PoseStamped goal, str planner_id='', bool use_start=False)
def followObjectByFrame(self, str frame, int max_duration=0)
def dockRobotByID(self, str dock_id, bool nav_to_dock=True)
def clearCostmapAroundRobot(self, float reset_distance)
def setInitialPose(self, PoseStamped initial_pose)
def clearGlobalCostmapAroundPose(self, PoseStamped pose, float reset_distance)
def getPathThroughPoses(self, PoseStamped start, list[PoseStamped] goals, str planner_id='', bool use_start=False)
def _routeFeedbackCallback(self, ComputeAndTrackRoute.Impl.FeedbackMessage msg)
def _amclPoseCallback(self, PoseWithCovarianceStamped msg)
def getAndTrackRoute(self, Union[int, PoseStamped] start, Union[int, PoseStamped] goal, bool use_start=False)
def getFeedback(self, RunningTask task=RunningTask.NONE)
def clearCostmapExceptRegion(self, float reset_distance)
def followObjectByTopic(self, str topic, int max_duration=0)
def _getRouteImpl(self, Union[int, PoseStamped] start, Union[int, PoseStamped] goal, bool use_start=False)
def _smoothPathImpl(self, Path path, str smoother_id='', float max_duration=2.0, bool check_for_collision=False)
def setTaskError(self, int error_code, str error_msg)
def goToPose(self, PoseStamped pose, str behavior_tree='')
def smoothPath(self, Path path, str smoother_id='', float max_duration=2.0, bool check_for_collision=False)
def followGpsWaypoints(self, list[GeoPose] gps_poses)