Nav2 Navigation Stack - lyrical  lyrical
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 
282  def followWaypoints(self, poses: list[PoseStamped]):
283  """Send a `FollowWaypoints` action request."""
284  self.clearPreviousStateclearPreviousState()
285  self.debugdebug("Waiting for 'FollowWaypoints' action server")
286  while not self.follow_waypoints_clientfollow_waypoints_client.wait_for_server(timeout_sec=1.0):
287  self.infoinfo("'FollowWaypoints' action server not available, waiting...")
288 
289  goal_msg = FollowWaypoints.Goal()
290  goal_msg.poses = poses
291 
292  self.infoinfo(f'Following {len(goal_msg.poses)} goals....')
293  send_goal_future = self.follow_waypoints_clientfollow_waypoints_client.send_goal_async(
294  goal_msg, self._feedbackCallback_feedbackCallback
295  )
296  rclpy.spin_until_future_complete(self, send_goal_future)
297  self.goal_handlegoal_handle = send_goal_future.result()
298 
299  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
300  msg = f'Following {len(poses)} waypoints request was rejected!'
301  self.setTaskErrorsetTaskError(FollowWaypoints.Result.UNKNOWN, msg)
302  self.errorerror(msg)
303  return None
304 
305  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
306  return RunningTask.FOLLOW_WAYPOINTS
307 
308  def followGpsWaypoints(self, gps_poses: list[GeoPose]):
309  """Send a `FollowGPSWaypoints` action request."""
310  self.clearPreviousStateclearPreviousState()
311  self.debugdebug("Waiting for 'FollowWaypoints' action server")
312  while not self.follow_gps_waypoints_clientfollow_gps_waypoints_client.wait_for_server(timeout_sec=1.0):
313  self.infoinfo("'FollowWaypoints' action server not available, waiting...")
314 
315  goal_msg = FollowGPSWaypoints.Goal()
316  goal_msg.gps_poses = gps_poses
317 
318  self.infoinfo(f'Following {len(goal_msg.gps_poses)} gps goals....')
319  send_goal_future = self.follow_gps_waypoints_clientfollow_gps_waypoints_client.send_goal_async(
320  goal_msg, self._feedbackCallback_feedbackCallback
321  )
322  rclpy.spin_until_future_complete(self, send_goal_future)
323  self.goal_handlegoal_handle = send_goal_future.result()
324 
325  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
326  msg = f'Following {len(gps_poses)} gps waypoints request was rejected!'
327  self.setTaskErrorsetTaskError(FollowGPSWaypoints.Result.UNKNOWN, msg)
328  self.errorerror(msg)
329  return None
330 
331  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
332  return RunningTask.FOLLOW_GPS_WAYPOINTS
333 
334  def spin(
335  self, spin_dist: float = 1.57, time_allowance: int = 10,
336  disable_collision_checks: bool = False):
337  self.clearPreviousStateclearPreviousState()
338  self.debugdebug("Waiting for 'Spin' action server")
339  while not self.spin_clientspin_client.wait_for_server(timeout_sec=1.0):
340  self.infoinfo("'Spin' action server not available, waiting...")
341  goal_msg = Spin.Goal()
342  goal_msg.target_yaw = spin_dist
343  goal_msg.time_allowance = Duration(sec=time_allowance)
344  goal_msg.disable_collision_checks = disable_collision_checks
345 
346  self.infoinfo(f'Spinning to angle {goal_msg.target_yaw}....')
347  send_goal_future = self.spin_clientspin_client.send_goal_async(
348  goal_msg, self._feedbackCallback_feedbackCallback
349  )
350  rclpy.spin_until_future_complete(self, send_goal_future)
351  self.goal_handlegoal_handle = send_goal_future.result()
352 
353  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
354  msg = 'Spin request was rejected!'
355  self.setTaskErrorsetTaskError(Spin.Result.UNKNOWN, msg)
356  self.errorerror(msg)
357  return None
358 
359  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
360  return RunningTask.SPIN
361 
362  def backup(
363  self, backup_dist: float = 0.15, backup_speed: float = 0.025,
364  time_allowance: int = 10,
365  disable_collision_checks: bool = False):
366  self.clearPreviousStateclearPreviousState()
367  self.debugdebug("Waiting for 'Backup' action server")
368  while not self.backup_clientbackup_client.wait_for_server(timeout_sec=1.0):
369  self.infoinfo("'Backup' action server not available, waiting...")
370  goal_msg = BackUp.Goal()
371  goal_msg.target = Point(x=float(backup_dist))
372  goal_msg.speed = backup_speed
373  goal_msg.time_allowance = Duration(sec=time_allowance)
374  goal_msg.disable_collision_checks = disable_collision_checks
375 
376  self.infoinfo(f'Backing up {goal_msg.target.x} m at {goal_msg.speed} m/s....')
377  send_goal_future = self.backup_clientbackup_client.send_goal_async(
378  goal_msg, self._feedbackCallback_feedbackCallback
379  )
380  rclpy.spin_until_future_complete(self, send_goal_future)
381  self.goal_handlegoal_handle = send_goal_future.result()
382 
383  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
384  msg = 'Backup request was rejected!'
385  self.setTaskErrorsetTaskError(BackUp.Result.UNKNOWN, msg)
386  self.errorerror(msg)
387  return None
388 
389  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
390  return RunningTask.BACKUP
391 
392  def driveOnHeading(
393  self, dist: float = 0.15, speed: float = 0.025,
394  time_allowance: int = 10,
395  disable_collision_checks: bool = False):
396  self.clearPreviousStateclearPreviousState()
397  self.debugdebug("Waiting for 'DriveOnHeading' action server")
398  while not self.drive_on_heading_clientdrive_on_heading_client.wait_for_server(timeout_sec=1.0):
399  self.infoinfo("'DriveOnHeading' action server not available, waiting...")
400  goal_msg = DriveOnHeading.Goal()
401  goal_msg.target = Point(x=float(dist))
402  goal_msg.speed = speed
403  goal_msg.time_allowance = Duration(sec=time_allowance)
404  goal_msg.disable_collision_checks = disable_collision_checks
405 
406  self.infoinfo(f'Drive {goal_msg.target.x} m on heading at {goal_msg.speed} m/s....')
407  send_goal_future = self.drive_on_heading_clientdrive_on_heading_client.send_goal_async(
408  goal_msg, self._feedbackCallback_feedbackCallback
409  )
410  rclpy.spin_until_future_complete(self, send_goal_future)
411  self.goal_handlegoal_handle = send_goal_future.result()
412 
413  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
414  msg = 'Drive On Heading request was rejected!'
415  self.setTaskErrorsetTaskError(DriveOnHeading.Result.UNKNOWN, msg)
416  self.errorerror(msg)
417  return None
418 
419  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
420  return RunningTask.DRIVE_ON_HEADING
421 
422  def assistedTeleop(self, time_allowance: int = 30):
423 
424  self.clearPreviousStateclearPreviousState()
425  self.debugdebug("Wanting for 'assisted_teleop' action server")
426 
427  while not self.assisted_teleop_clientassisted_teleop_client.wait_for_server(timeout_sec=1.0):
428  self.infoinfo("'assisted_teleop' action server not available, waiting...")
429  goal_msg = AssistedTeleop.Goal()
430  goal_msg.time_allowance = Duration(sec=time_allowance)
431 
432  self.infoinfo("Running 'assisted_teleop'....")
433  send_goal_future = self.assisted_teleop_clientassisted_teleop_client.send_goal_async(
434  goal_msg, self._feedbackCallback_feedbackCallback
435  )
436  rclpy.spin_until_future_complete(self, send_goal_future)
437  self.goal_handlegoal_handle = send_goal_future.result()
438 
439  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
440  msg = 'Assisted Teleop request was rejected!'
441  self.setTaskErrorsetTaskError(AssistedTeleop.Result.UNKNOWN, msg)
442  self.errorerror(msg)
443  return None
444 
445  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
446  return RunningTask.ASSISTED_TELEOP
447 
448  def followPath(self, path: Path, controller_id: str = '',
449  goal_checker_id: str = '', progress_checker_id: str = '',
450  path_handler_id: str = ''):
451  self.clearPreviousStateclearPreviousState()
452  """Send a `FollowPath` action request."""
453  self.debugdebug("Waiting for 'FollowPath' action server")
454  while not self.follow_path_clientfollow_path_client.wait_for_server(timeout_sec=1.0):
455  self.infoinfo("'FollowPath' action server not available, waiting...")
456 
457  goal_msg = FollowPath.Goal()
458  goal_msg.path = path
459  goal_msg.controller_id = controller_id
460  goal_msg.goal_checker_id = goal_checker_id
461  goal_msg.progress_checker_id = progress_checker_id
462  goal_msg.path_handler_id = path_handler_id
463 
464  self.infoinfo('Executing path...')
465  send_goal_future = self.follow_path_clientfollow_path_client.send_goal_async(
466  goal_msg, self._feedbackCallback_feedbackCallback
467  )
468  rclpy.spin_until_future_complete(self, send_goal_future)
469  self.goal_handlegoal_handle = send_goal_future.result()
470 
471  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
472  msg = 'FollowPath goal was rejected!'
473  self.setTaskErrorsetTaskError(FollowPath.Result.UNKNOWN, msg)
474  self.errorerror(msg)
475  return None
476 
477  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
478  return RunningTask.FOLLOW_PATH
479 
480  def dockRobotByPose(self, dock_pose: PoseStamped,
481  dock_type: str = '', nav_to_dock: bool = True):
482  self.clearPreviousStateclearPreviousState()
483  """Send a `DockRobot` action request."""
484  self.infoinfo("Waiting for 'DockRobot' action server")
485  while not self.docking_clientdocking_client.wait_for_server(timeout_sec=1.0):
486  self.infoinfo('"DockRobot" action server not available, waiting...')
487 
488  goal_msg = DockRobot.Goal()
489  goal_msg.use_dock_id = False
490  goal_msg.dock_pose = dock_pose
491  goal_msg.dock_type = dock_type
492  goal_msg.navigate_to_staging_pose = nav_to_dock # if want to navigate before staging
493 
494  self.infoinfo('Docking at pose: ' + str(dock_pose) + '...')
495  send_goal_future = self.docking_clientdocking_client.send_goal_async(
496  goal_msg, self._feedbackCallback_feedbackCallback)
497  rclpy.spin_until_future_complete(self, send_goal_future)
498  self.goal_handlegoal_handle = send_goal_future.result()
499 
500  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
501  msg = 'DockRobot request was rejected!'
502  self.setTaskErrorsetTaskError(DockRobot.Result.UNKNOWN, msg)
503  self.errorerror(msg)
504  return None
505 
506  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
507  return RunningTask.DOCK_ROBOT
508 
509  def dockRobotByID(self, dock_id: str, nav_to_dock: bool = True):
510  """Send a `DockRobot` action request."""
511  self.clearPreviousStateclearPreviousState()
512  self.infoinfo("Waiting for 'DockRobot' action server")
513  while not self.docking_clientdocking_client.wait_for_server(timeout_sec=1.0):
514  self.infoinfo('"DockRobot" action server not available, waiting...')
515 
516  goal_msg = DockRobot.Goal()
517  goal_msg.use_dock_id = True
518  goal_msg.dock_id = dock_id
519  goal_msg.navigate_to_staging_pose = nav_to_dock # if want to navigate before staging
520 
521  self.infoinfo('Docking at dock ID: ' + str(dock_id) + '...')
522  send_goal_future = self.docking_clientdocking_client.send_goal_async(
523  goal_msg, self._feedbackCallback_feedbackCallback)
524  rclpy.spin_until_future_complete(self, send_goal_future)
525  self.goal_handlegoal_handle = send_goal_future.result()
526 
527  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
528  msg = 'DockRobot request was rejected!'
529  self.setTaskErrorsetTaskError(DockRobot.Result.UNKNOWN, msg)
530  self.errorerror(msg)
531  return None
532 
533  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
534  return RunningTask.DOCK_ROBOT
535 
536  def undockRobot(self, dock_type: str = ''):
537  """Send a `UndockRobot` action request."""
538  self.clearPreviousStateclearPreviousState()
539  self.infoinfo("Waiting for 'UndockRobot' action server")
540  while not self.undocking_clientundocking_client.wait_for_server(timeout_sec=1.0):
541  self.infoinfo('"UndockRobot" action server not available, waiting...')
542 
543  goal_msg = UndockRobot.Goal()
544  goal_msg.dock_type = dock_type
545 
546  self.infoinfo('Undocking from dock of type: ' + str(dock_type) + '...')
547  send_goal_future = self.undocking_clientundocking_client.send_goal_async(
548  goal_msg, self._feedbackCallback_feedbackCallback)
549  rclpy.spin_until_future_complete(self, send_goal_future)
550  self.goal_handlegoal_handle = send_goal_future.result()
551 
552  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
553  msg = 'UndockRobot request was rejected!'
554  self.setTaskErrorsetTaskError(UndockRobot.Result.UNKNOWN, msg)
555  self.errorerror(msg)
556  return None
557 
558  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
559  return RunningTask.UNDOCK_ROBOT
560 
561  def followObjectByTopic(self, topic: str, max_duration: int = 0):
562  """Send a `FollowObject` action request."""
563  self.clearPreviousStateclearPreviousState()
564  self.infoinfo("Waiting for 'FollowObject' action server")
565  while not self.following_clientfollowing_client.wait_for_server(timeout_sec=1.0):
566  self.infoinfo('"FollowObject" action server not available, waiting...')
567 
568  goal_msg = FollowObject.Goal()
569  goal_msg.pose_topic = topic
570  goal_msg.max_duration = Duration(sec=max_duration)
571 
572  self.infoinfo('Following object on topic: ' + str(topic) + '...')
573  send_goal_future = self.following_clientfollowing_client.send_goal_async(
574  goal_msg, self._feedbackCallback_feedbackCallback)
575  rclpy.spin_until_future_complete(self, send_goal_future)
576  self.goal_handlegoal_handle = send_goal_future.result()
577 
578  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
579  msg = 'FollowObject request was rejected!'
580  self.setTaskErrorsetTaskError(FollowObject.Result.UNKNOWN, msg)
581  self.errorerror(msg)
582  return None
583 
584  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
585  return RunningTask.FOLLOW_OBJECT
586 
587  def followObjectByFrame(self, frame: str, max_duration: int = 0):
588  """Send a `FollowObject` action request."""
589  self.clearPreviousStateclearPreviousState()
590  self.infoinfo("Waiting for 'FollowObject' action server")
591  while not self.following_clientfollowing_client.wait_for_server(timeout_sec=1.0):
592  self.infoinfo('"FollowObject" action server not available, waiting...')
593 
594  goal_msg = FollowObject.Goal()
595  goal_msg.tracked_frame = frame
596  goal_msg.max_duration = Duration(sec=max_duration)
597 
598  self.infoinfo('Following object in frame: ' + str(frame) + '...')
599  send_goal_future = self.following_clientfollowing_client.send_goal_async(
600  goal_msg, self._feedbackCallback_feedbackCallback)
601  rclpy.spin_until_future_complete(self, send_goal_future)
602  self.goal_handlegoal_handle = send_goal_future.result()
603 
604  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
605  msg = 'FollowObject request was rejected!'
606  self.setTaskErrorsetTaskError(FollowObject.Result.UNKNOWN, msg)
607  self.errorerror(msg)
608  return None
609 
610  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
611  return RunningTask.FOLLOW_OBJECT
612 
613  def cancelTask(self):
614  """Cancel pending task request of any type."""
615  self.infoinfo('Canceling current task.')
616  if self.result_futureresult_future:
617  if self.goal_handlegoal_handle is not None:
618  future = self.goal_handlegoal_handle.cancel_goal_async()
619  rclpy.spin_until_future_complete(self, future)
620  else:
621  self.errorerror('Cancel task failed, goal handle is None')
622  self.setTaskErrorsetTaskError(0, 'Cancel task failed, goal handle is None')
623  return
624  if self.route_result_futureroute_result_future:
625  if self.route_goal_handleroute_goal_handle is not None:
626  future = self.route_goal_handleroute_goal_handle.cancel_goal_async()
627  rclpy.spin_until_future_complete(self, future)
628  else:
629  self.errorerror('Cancel route task failed, goal handle is None')
630  self.setTaskErrorsetTaskError(0, 'Cancel route task failed, goal handle is None')
631  return
632  self.clearPreviousStateclearPreviousState()
633  return
634 
635  def isTaskComplete(self, task: RunningTask = RunningTask.NONE):
636  """Check if the task request of any type is complete yet."""
637  # Find the result future to spin
638  if task is None:
639  self.errorerror('Task is None, cannot check for completion')
640  return False
641 
642  result_future = None
643  if task != RunningTask.COMPUTE_AND_TRACK_ROUTE:
644  result_future = self.result_futureresult_future
645  else:
646  result_future = self.route_result_futureroute_result_future
647  if not result_future:
648  # task was cancelled or completed
649  return True
650 
651  # Get the result of the future, if complete
652  rclpy.spin_until_future_complete(self, result_future, timeout_sec=0.10)
653  result_response = result_future.result()
654 
655  if result_response:
656  self.statusstatus = result_response.status
657  if self.statusstatus != GoalStatus.STATUS_SUCCEEDED:
658  result = result_response.result
659  if result is not None:
660  self.setTaskErrorsetTaskError(result.error_code, result.error_msg)
661  self.debugdebug(
662  'Task with failed with'
663  f' status code:{self.status}'
664  f' error code:{result.error_code}'
665  f' error msg:{result.error_msg}')
666  return True
667  else:
668  self.setTaskErrorsetTaskError(0, 'No result received')
669  self.debugdebug('Task failed with no result received')
670  return True
671  else:
672  # Timed out, still processing, not complete yet
673  return False
674 
675  self.debugdebug('Task succeeded!')
676  return True
677 
678  def getFeedback(self, task: RunningTask = RunningTask.NONE):
679  """Get the pending action feedback message."""
680  if task != RunningTask.COMPUTE_AND_TRACK_ROUTE:
681  return self.feedbackfeedback
682  if len(self.route_feedbackroute_feedback) > 0:
683  return self.route_feedbackroute_feedback.pop(0)
684  return None
685 
686  def getResult(self):
687  """Get the pending action result message."""
688  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
689  return TaskResult.SUCCEEDED
690  elif self.statusstatus == GoalStatus.STATUS_ABORTED:
691  return TaskResult.FAILED
692  elif self.statusstatus == GoalStatus.STATUS_CANCELED:
693  return TaskResult.CANCELED
694  else:
695  return TaskResult.UNKNOWN
696 
697  def clearPreviousState(self):
698  self.feedbackfeedback = None
699  self.last_action_error_codelast_action_error_code = 0
700  self.last_action_error_msglast_action_error_msg = ''
701 
702  def setTaskError(self, error_code: int, error_msg: str):
703  self.last_action_error_codelast_action_error_code = error_code
704  self.last_action_error_msglast_action_error_msg = error_msg
705 
706  def getTaskError(self):
707  return (self.last_action_error_codelast_action_error_code, self.last_action_error_msglast_action_error_msg)
708 
709  def waitUntilNav2Active(self, navigator: str = 'bt_navigator',
710  localizer: str = 'amcl'):
711  """Block until the full navigation system is up and running."""
712  if localizer != 'robot_localization': # non-lifecycle node
713  self._waitForNodeToActivate_waitForNodeToActivate(localizer)
714  if localizer == 'amcl':
715  self._waitForInitialPose_waitForInitialPose()
716  self._waitForNodeToActivate_waitForNodeToActivate(navigator)
717  self.infoinfo('Nav2 is ready for use!')
718  return
719 
720  def _getPathImpl(
721  self, start: PoseStamped, goal: PoseStamped,
722  planner_id: str = '', use_start: bool = False
723  ):
724  """
725  Send a `ComputePathToPose` action request.
726 
727  Internal implementation to get the full result, not just the path.
728  """
729  self.debugdebug("Waiting for 'ComputePathToPose' action server")
730  while not self.compute_path_to_pose_clientcompute_path_to_pose_client.wait_for_server(timeout_sec=1.0):
731  self.infoinfo("'ComputePathToPose' action server not available, waiting...")
732 
733  goal_msg = ComputePathToPose.Goal()
734  goal_msg.start = start
735  goal_msg.goal = goal
736  goal_msg.planner_id = planner_id
737  goal_msg.use_start = use_start
738 
739  self.infoinfo('Getting path...')
740  send_goal_future = self.compute_path_to_pose_clientcompute_path_to_pose_client.send_goal_async(goal_msg)
741  rclpy.spin_until_future_complete(self, send_goal_future)
742  self.goal_handlegoal_handle = send_goal_future.result()
743 
744  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
745  self.errorerror('Get path was rejected!')
746  self.statusstatus = GoalStatus.STATUS_UNKNOWN
747  result = ComputePathToPose.Result()
748  result.error_code = ComputePathToPose.Result.UNKNOWN
749  result.error_msg = 'Get path was rejected'
750  return result
751 
752  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
753  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
754  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
755 
756  return self.result_futureresult_future.result().result # type: ignore[union-attr]
757 
758  def getPath(
759  self, start: PoseStamped, goal: PoseStamped,
760  planner_id: str = '', use_start: bool = False):
761  """Send a `ComputePathToPose` action request."""
762  self.clearPreviousStateclearPreviousState()
763  rtn = self._getPathImpl_getPathImpl(start, goal, planner_id, use_start)
764 
765  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
766  return rtn.path
767  else:
768  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
769  self.warnwarn('Getting path failed with'
770  f' status code:{self.status}'
771  f' error code:{rtn.error_code}'
772  f' error msg:{rtn.error_msg}')
773  return None
774 
775  def _getPathThroughPosesImpl(
776  self, start: PoseStamped, goals: list[PoseStamped],
777  planner_id: str = '', use_start: bool = False
778  ):
779  """
780  Send a `ComputePathThroughPoses` action request.
781 
782  Internal implementation to get the full result, not just the path.
783  """
784  self.debugdebug("Waiting for 'ComputePathThroughPoses' action server")
785  while not self.compute_path_through_poses_clientcompute_path_through_poses_client.wait_for_server(
786  timeout_sec=1.0
787  ):
788  self.infoinfo(
789  "'ComputePathThroughPoses' action server not available, waiting..."
790  )
791 
792  goal_msg = ComputePathThroughPoses.Goal()
793  goal_msg.start = start
794  goal_msg.goals.header.frame_id = 'map'
795  goal_msg.goals.header.stamp = self.get_clock().now().to_msg()
796  goal_msg.goals.goals = goals
797  goal_msg.planner_id = planner_id
798  goal_msg.use_start = use_start
799 
800  self.infoinfo('Getting path...')
801  send_goal_future = self.compute_path_through_poses_clientcompute_path_through_poses_client.send_goal_async(
802  goal_msg
803  )
804  rclpy.spin_until_future_complete(self, send_goal_future)
805  self.goal_handlegoal_handle = send_goal_future.result()
806 
807  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
808  self.errorerror('Get path was rejected!')
809  result = ComputePathThroughPoses.Result()
810  result.error_code = ComputePathThroughPoses.Result.UNKNOWN
811  result.error_msg = 'Get path was rejected!'
812  return result
813 
814  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
815  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
816  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
817 
818  return self.result_futureresult_future.result().result # type: ignore[union-attr]
819 
821  self, start: PoseStamped, goals: list[PoseStamped],
822  planner_id: str = '', use_start: bool = False):
823  """Send a `ComputePathThroughPoses` action request."""
824  self.clearPreviousStateclearPreviousState()
825  rtn = self._getPathThroughPosesImpl_getPathThroughPosesImpl(start, goals, planner_id, use_start)
826 
827  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
828  return rtn.path
829  else:
830  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
831  self.warnwarn('Getting path failed with'
832  f' status code:{self.status}'
833  f' error code:{rtn.error_code}'
834  f' error msg:{rtn.error_msg}')
835  return None
836 
837  def _getRouteImpl(
838  self, start: Union[int, PoseStamped],
839  goal: Union[int, PoseStamped], use_start: bool = False
840  ):
841  """
842  Send a `ComputeRoute` action request.
843 
844  Internal implementation to get the full result, not just the sparse route and dense path.
845  """
846  self.debugdebug("Waiting for 'ComputeRoute' action server")
847  while not self.compute_route_clientcompute_route_client.wait_for_server(timeout_sec=1.0):
848  self.infoinfo("'ComputeRoute' action server not available, waiting...")
849 
850  goal_msg = ComputeRoute.Goal()
851  goal_msg.use_start = use_start
852 
853  # Support both ID based requests and PoseStamped based requests
854  if isinstance(start, int) and isinstance(goal, int):
855  goal_msg.start_id = start
856  goal_msg.goal_id = goal
857  goal_msg.use_poses = False
858  elif isinstance(start, PoseStamped) and isinstance(goal, PoseStamped):
859  goal_msg.start = start
860  goal_msg.goal = goal
861  goal_msg.use_poses = True
862  else:
863  self.errorerror('Invalid start and goal types. Must be PoseStamped for pose or int for ID')
864  result = ComputeRoute.Result()
865  result.error_code = ComputeRoute.Result.UNKNOWN
866  result.error_msg = 'Request type fields were invalid!'
867  return result
868 
869  self.infoinfo('Getting route...')
870  send_goal_future = self.compute_route_clientcompute_route_client.send_goal_async(goal_msg)
871  rclpy.spin_until_future_complete(self, send_goal_future)
872  self.goal_handlegoal_handle = send_goal_future.result()
873 
874  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
875  self.errorerror('Get route was rejected!')
876  result = ComputeRoute.Result()
877  result.error_code = ComputeRoute.Result.UNKNOWN
878  result.error_msg = 'Get route was rejected!'
879  return result
880 
881  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
882  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
883  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
884 
885  return self.result_futureresult_future.result().result # type: ignore[union-attr]
886 
887  def getRoute(
888  self, start: Union[int, PoseStamped],
889  goal: Union[int, PoseStamped],
890  use_start: bool = False):
891  """Send a `ComputeRoute` action request."""
892  self.clearPreviousStateclearPreviousState()
893  rtn = self._getRouteImpl_getRouteImpl(start, goal, use_start=False)
894 
895  if self.statusstatus != GoalStatus.STATUS_SUCCEEDED:
896  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
897  self.warnwarn(
898  'Getting route failed with'
899  f' status code:{self.status}'
900  f' error code:{rtn.error_code}'
901  f' error msg:{rtn.error_msg}')
902  return None
903 
904  return [rtn.path, rtn.route]
905 
907  self, start: Union[int, PoseStamped],
908  goal: Union[int, PoseStamped], use_start: bool = False
909  ):
910  """Send a `ComputeAndTrackRoute` action request."""
911  self.clearPreviousStateclearPreviousState()
912  self.debugdebug("Waiting for 'ComputeAndTrackRoute' action server")
913  while not self.compute_and_track_route_clientcompute_and_track_route_client.wait_for_server(timeout_sec=1.0):
914  self.infoinfo("'ComputeAndTrackRoute' action server not available, waiting...")
915 
916  goal_msg = ComputeAndTrackRoute.Goal()
917  goal_msg.use_start = use_start
918 
919  # Support both ID based requests and PoseStamped based requests
920  if isinstance(start, int) and isinstance(goal, int):
921  goal_msg.start_id = start
922  goal_msg.goal_id = goal
923  goal_msg.use_poses = False
924  elif isinstance(start, PoseStamped) and isinstance(goal, PoseStamped):
925  goal_msg.start = start
926  goal_msg.goal = goal
927  goal_msg.use_poses = True
928  else:
929  self.setTaskErrorsetTaskError(ComputeAndTrackRoute.Result.UNKNOWN,
930  'Request type fields were invalid!')
931  self.errorerror('Invalid start and goal types. Must be PoseStamped for pose or int for ID')
932  return None
933 
934  self.infoinfo('Computing and tracking route...')
935  send_goal_future = self.compute_and_track_route_clientcompute_and_track_route_client.send_goal_async(goal_msg,
936  self._routeFeedbackCallback_routeFeedbackCallback) # noqa: E128
937  rclpy.spin_until_future_complete(self, send_goal_future)
938  self.route_goal_handleroute_goal_handle = send_goal_future.result()
939 
940  if not self.route_goal_handleroute_goal_handle or not self.route_goal_handleroute_goal_handle.accepted:
941  msg = 'Compute and track route was rejected!'
942  self.setTaskErrorsetTaskError(ComputeAndTrackRoute.Result.UNKNOWN, msg)
943  self.errorerror(msg)
944  return None
945 
946  self.route_result_futureroute_result_future = self.route_goal_handleroute_goal_handle.get_result_async()
947  return RunningTask.COMPUTE_AND_TRACK_ROUTE
948 
949  def _smoothPathImpl(
950  self, path: Path, smoother_id: str = '',
951  max_duration: float = 2.0, check_for_collision: bool = False
952  ):
953  """
954  Send a `SmoothPath` action request.
955 
956  Internal implementation to get the full result, not just the path.
957  """
958  self.debugdebug("Waiting for 'SmoothPath' action server")
959  while not self.smoother_clientsmoother_client.wait_for_server(timeout_sec=1.0):
960  self.infoinfo("'SmoothPath' action server not available, waiting...")
961 
962  goal_msg = SmoothPath.Goal()
963  goal_msg.path = path
964  goal_msg.max_smoothing_duration = rclpyDuration(seconds=max_duration).to_msg()
965  goal_msg.smoother_id = smoother_id
966  goal_msg.check_for_collisions = check_for_collision
967 
968  self.infoinfo('Smoothing path...')
969  send_goal_future = self.smoother_clientsmoother_client.send_goal_async(goal_msg)
970  rclpy.spin_until_future_complete(self, send_goal_future)
971  self.goal_handlegoal_handle = send_goal_future.result()
972 
973  if not self.goal_handlegoal_handle or not self.goal_handlegoal_handle.accepted:
974  self.errorerror('Smooth path was rejected!')
975  result = SmoothPath.Result()
976  result.error_code = SmoothPath.Result.UNKNOWN
977  result.error_msg = 'Smooth path was rejected'
978  return result
979 
980  self.result_futureresult_future = self.goal_handlegoal_handle.get_result_async()
981  rclpy.spin_until_future_complete(self, self.result_futureresult_future)
982  self.statusstatus = self.result_futureresult_future.result().status # type: ignore[union-attr]
983 
984  return self.result_futureresult_future.result().result # type: ignore[union-attr]
985 
987  self, path: Path, smoother_id: str = '',
988  max_duration: float = 2.0, check_for_collision: bool = False):
989  """Send a `SmoothPath` action request."""
990  self.clearPreviousStateclearPreviousState()
991  rtn = self._smoothPathImpl_smoothPathImpl(path, smoother_id, max_duration, check_for_collision)
992 
993  if self.statusstatus == GoalStatus.STATUS_SUCCEEDED:
994  return rtn.path
995  else:
996  self.setTaskErrorsetTaskError(rtn.error_code, rtn.error_msg)
997  self.warnwarn('Getting path failed with'
998  f' status code:{self.status}'
999  f' error code:{rtn.error_code}'
1000  f' error msg:{rtn.error_msg}')
1001  return None
1002 
1003  def changeMap(self, map_filepath: str):
1004  """Change the current static map in the map server."""
1005  while not self.change_maps_srvchange_maps_srv.wait_for_service(timeout_sec=1.0):
1006  self.infoinfo('change map service not available, waiting...')
1007  req = LoadMap.Request()
1008  req.map_url = map_filepath
1009  future = self.change_maps_srvchange_maps_srv.call_async(req)
1010  rclpy.spin_until_future_complete(self, future)
1011 
1012  future_result = future.result()
1013  if future_result is None:
1014  self.errorerror('Change map request failed!')
1015  return False
1016 
1017  result = future_result.result
1018  if result != LoadMap.Response.RESULT_SUCCESS:
1019  if result == LoadMap.Response.RESULT_MAP_DOES_NOT_EXIST:
1020  reason = 'Map does not exist'
1021  elif result == LoadMap.Response.RESULT_INVALID_MAP_DATA:
1022  reason = 'Invalid map data'
1023  elif result == LoadMap.Response.RESULT_INVALID_MAP_METADATA:
1024  reason = 'Invalid map metadata'
1025  elif result == LoadMap.Response.RESULT_UNDEFINED_FAILURE:
1026  reason = 'Undefined failure'
1027  else:
1028  reason = 'Unknown'
1029  self.setTaskErrorsetTaskError(result, reason)
1030  self.errorerror(f'Change map request failed:{reason}!')
1031  return False
1032  else:
1033  self.infoinfo('Change map request was successful!')
1034  return True
1035 
1036  def clearAllCostmaps(self):
1037  """Clear all costmaps."""
1038  self.clearLocalCostmapclearLocalCostmap()
1039  self.clearGlobalCostmapclearGlobalCostmap()
1040  return
1041 
1043  """Clear local costmap."""
1044  while not self.clear_costmap_local_srvclear_costmap_local_srv.wait_for_service(timeout_sec=1.0):
1045  self.infoinfo('Clear local costmaps service not available, waiting...')
1046  req = ClearEntireCostmap.Request()
1047  future = self.clear_costmap_local_srvclear_costmap_local_srv.call_async(req)
1048  rclpy.spin_until_future_complete(self, future)
1049 
1050  result = future.result()
1051  if result is None:
1052  self.errorerror('Clear local costmap request failed!')
1053 
1054  return
1055 
1057  """Clear global costmap."""
1058  while not self.clear_costmap_global_srvclear_costmap_global_srv.wait_for_service(timeout_sec=1.0):
1059  self.infoinfo('Clear global costmaps service not available, waiting...')
1060  req = ClearEntireCostmap.Request()
1061  future = self.clear_costmap_global_srvclear_costmap_global_srv.call_async(req)
1062  rclpy.spin_until_future_complete(self, future)
1063 
1064  result = future.result()
1065  if result is None:
1066  self.errorerror('Clear global costmap request failed!')
1067 
1068  return
1069 
1070  def clearCostmapExceptRegion(self, reset_distance: float):
1071  """Clear the costmap except for a specified region."""
1072  while not self.clear_costmap_except_region_srvclear_costmap_except_region_srv.wait_for_service(timeout_sec=1.0):
1073  self.infoinfo('ClearCostmapExceptRegion service not available, waiting...')
1074  req = ClearCostmapExceptRegion.Request()
1075  req.reset_distance = reset_distance
1076  future = self.clear_costmap_except_region_srvclear_costmap_except_region_srv.call_async(req)
1077  rclpy.spin_until_future_complete(self, future)
1078 
1079  result = future.result()
1080  if result is None:
1081  self.errorerror('Clear costmap except region request failed!')
1082 
1083  return
1084 
1085  def clearCostmapAroundRobot(self, reset_distance: float):
1086  """Clear the costmap around the robot."""
1087  while not self.clear_costmap_around_robot_srvclear_costmap_around_robot_srv.wait_for_service(timeout_sec=1.0):
1088  self.infoinfo('ClearCostmapAroundRobot service not available, waiting...')
1089  req = ClearCostmapAroundRobot.Request()
1090  req.reset_distance = reset_distance
1091  future = self.clear_costmap_around_robot_srvclear_costmap_around_robot_srv.call_async(req)
1092  rclpy.spin_until_future_complete(self, future)
1093 
1094  result = future.result()
1095  if result is None:
1096  self.errorerror('Clear costmap around robot request failed!')
1097 
1098  return
1099 
1100  def clearLocalCostmapAroundPose(self, pose: PoseStamped, reset_distance: float):
1101  """Clear the costmap around a given pose."""
1102  while not self.clear_local_costmap_around_pose_srvclear_local_costmap_around_pose_srv.wait_for_service(timeout_sec=1.0):
1103  self.infoinfo('ClearLocalCostmapAroundPose service not available, waiting...')
1104  req = ClearCostmapAroundPose.Request()
1105  req.pose = pose
1106  req.reset_distance = reset_distance
1107  future = self.clear_local_costmap_around_pose_srvclear_local_costmap_around_pose_srv.call_async(req)
1108  rclpy.spin_until_future_complete(self, future)
1109 
1110  result = future.result()
1111  if result is None:
1112  self.errorerror('Clear local costmap around pose request failed!')
1113 
1114  return
1115 
1116  def clearGlobalCostmapAroundPose(self, pose: PoseStamped, reset_distance: float):
1117  """Clear the global costmap around a given pose."""
1118  while not self.clear_global_costmap_around_pose_srvclear_global_costmap_around_pose_srv.wait_for_service(timeout_sec=1.0):
1119  self.infoinfo('ClearGlobalCostmapAroundPose service not available, waiting...')
1120  req = ClearCostmapAroundPose.Request()
1121  req.pose = pose
1122  req.reset_distance = reset_distance
1123  future = self.clear_global_costmap_around_pose_srvclear_global_costmap_around_pose_srv.call_async(req)
1124  rclpy.spin_until_future_complete(self, future)
1125 
1126  result = future.result()
1127  if result is None:
1128  self.errorerror('Clear global costmap around pose request failed!')
1129 
1130  return
1131 
1132  def getGlobalCostmap(self):
1133  """Get the global costmap."""
1134  while not self.get_costmap_global_srvget_costmap_global_srv.wait_for_service(timeout_sec=1.0):
1135  self.infoinfo('Get global costmaps service not available, waiting...')
1136  req = GetCostmap.Request()
1137  future = self.get_costmap_global_srvget_costmap_global_srv.call_async(req)
1138  rclpy.spin_until_future_complete(self, future)
1139 
1140  result = future.result()
1141  if result is None:
1142  self.errorerror('Get global costmap request failed!')
1143  return None
1144 
1145  return result.map
1146 
1147  def getLocalCostmap(self):
1148  """Get the local costmap."""
1149  while not self.get_costmap_local_srvget_costmap_local_srv.wait_for_service(timeout_sec=1.0):
1150  self.infoinfo('Get local costmaps service not available, waiting...')
1151  req = GetCostmap.Request()
1152  future = self.get_costmap_local_srvget_costmap_local_srv.call_async(req)
1153  rclpy.spin_until_future_complete(self, future)
1154 
1155  result = future.result()
1156 
1157  if result is None:
1158  self.errorerror('Get local costmap request failed!')
1159  return None
1160 
1161  return result.map
1162 
1163  def toggleCollisionMonitor(self, enable: bool):
1164  """Toggle the collision monitor."""
1165  while not self.toggle_collision_monitor_srvtoggle_collision_monitor_srv.wait_for_service(timeout_sec=1.0):
1166  self.infoinfo('Toggle collision monitor service not available, waiting...')
1167  req = Toggle.Request()
1168  req.enable = enable
1169  future = self.toggle_collision_monitor_srvtoggle_collision_monitor_srv.call_async(req)
1170 
1171  rclpy.spin_until_future_complete(self, future)
1172  result = future.result()
1173  if result is None:
1174  self.errorerror('Toggle collision monitor request failed!')
1175 
1176  return
1177 
1178  def lifecycleStartup(self):
1179  """Startup nav2 lifecycle system."""
1180  self.infoinfo('Starting up lifecycle nodes based on lifecycle_manager.')
1181  for srv_name, srv_type in self.get_service_names_and_types():
1182  if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
1183  self.infoinfo(f'Starting up {srv_name}')
1184  mgr_client: Client[ManageLifecycleNodes.Request, ManageLifecycleNodes.Response] = \
1185  self.create_client(ManageLifecycleNodes, srv_name)
1186  while not mgr_client.wait_for_service(timeout_sec=1.0):
1187  self.infoinfo(f'{srv_name} service not available, waiting...')
1188  req = ManageLifecycleNodes.Request()
1189  req.command = ManageLifecycleNodes.Request.STARTUP
1190  future = mgr_client.call_async(req)
1191 
1192  # starting up requires a full map->odom->base_link TF tree
1193  # so if we're not successful, try forwarding the initial pose
1194  while True:
1195  rclpy.spin_until_future_complete(self, future, timeout_sec=0.10)
1196  if not future:
1197  self._waitForInitialPose_waitForInitialPose()
1198  else:
1199  break
1200  self.infoinfo('Nav2 is ready for use!')
1201  return
1202 
1204  """Shutdown nav2 lifecycle system."""
1205  self.infoinfo('Shutting down lifecycle nodes based on lifecycle_manager.')
1206  for srv_name, srv_type in self.get_service_names_and_types():
1207  if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
1208  self.infoinfo(f'Shutting down {srv_name}')
1209  mgr_client: Client[ManageLifecycleNodes.Request, ManageLifecycleNodes.Response] = \
1210  self.create_client(ManageLifecycleNodes, srv_name)
1211  while not mgr_client.wait_for_service(timeout_sec=1.0):
1212  self.infoinfo(f'{srv_name} service not available, waiting...')
1213  req = ManageLifecycleNodes.Request()
1214  req.command = ManageLifecycleNodes.Request.SHUTDOWN
1215  future = mgr_client.call_async(req)
1216  rclpy.spin_until_future_complete(self, future)
1217  future.result()
1218  return
1219 
1220  def _waitForNodeToActivate(self, node_name: str):
1221  # Waits for the node within the tester namespace to become active
1222  self.debugdebug(f'Waiting for {node_name} to become active..')
1223  node_service = f'{node_name}/get_state'
1224  state_client: Client[GetState.Request, GetState.Response] = \
1225  self.create_client(GetState, node_service)
1226  while not state_client.wait_for_service(timeout_sec=1.0):
1227  self.infoinfo(f'{node_service} service not available, waiting...')
1228 
1229  req = GetState.Request()
1230  state = 'unknown'
1231  while state != 'active':
1232  self.debugdebug(f'Getting {node_name} state...')
1233  future = state_client.call_async(req)
1234  rclpy.spin_until_future_complete(self, future)
1235 
1236  result = future.result()
1237  if result is not None:
1238  state = result.current_state.label
1239  self.debugdebug(f'Result of get_state: {state}')
1240  time.sleep(2)
1241  return
1242 
1243  def _waitForInitialPose(self):
1244  while not self.initial_pose_receivedinitial_pose_received:
1245  self.infoinfo('Setting initial pose')
1246  self._setInitialPose_setInitialPose()
1247  self.infoinfo('Waiting for amcl_pose to be received')
1248  rclpy.spin_once(self, timeout_sec=1.0)
1249  return
1250 
1251  def _amclPoseCallback(self, msg: PoseWithCovarianceStamped):
1252  self.debugdebug('Received amcl pose')
1253  self.initial_pose_receivedinitial_pose_received = True
1254  return
1255 
1256  def _feedbackCallback(self, msg: Any):
1257  self.debugdebug('Received action feedback message')
1258  self.feedbackfeedback = msg.feedback
1259  return
1260 
1261  def _routeFeedbackCallback(
1262  self, msg: ComputeAndTrackRoute.Impl.FeedbackMessage):
1263  self.debugdebug('Received route action feedback message')
1264  self.route_feedbackroute_feedback.append(msg.feedback)
1265  return
1266 
1267  def _setInitialPose(self):
1268  msg = PoseWithCovarianceStamped()
1269  msg.pose.pose = self.initial_poseinitial_pose.pose
1270  msg.header.frame_id = self.initial_poseinitial_pose.header.frame_id
1271  msg.header.stamp = self.initial_poseinitial_pose.header.stamp
1272  self.infoinfo('Publishing Initial Pose')
1273  self.initial_pose_pubinitial_pose_pub.publish(msg)
1274  return
1275 
1276  def info(self, msg: str):
1277  self.get_logger().info(msg)
1278  return
1279 
1280  def warn(self, msg: str):
1281  self.get_logger().warning(msg)
1282  return
1283 
1284  def error(self, msg: str):
1285  self.get_logger().error(msg)
1286  return
1287 
1288  def debug(self, msg: str):
1289  self.get_logger().debug(msg)
1290  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 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 followWaypoints(self, list[PoseStamped] poses)
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)