add humble-navigation2
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
# Copyright 2022 Stevedan Ogochukwu Omodolor
|
||||
# Copyright 2022 Jaehun Jackson Kim
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
This is a Python3 API for costmap 2d messages from the stack.
|
||||
|
||||
It provides the basic conversion, get/set,
|
||||
and handling semantics found in the costmap 2d C++ API.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class PyCostmap2D:
|
||||
"""
|
||||
PyCostmap2D.
|
||||
|
||||
Costmap Python3 API for OccupancyGrids to populate from published messages
|
||||
"""
|
||||
|
||||
def __init__(self, occupancy_map):
|
||||
"""
|
||||
Initialize costmap2D.
|
||||
|
||||
Args:
|
||||
----
|
||||
occupancy_map (OccupancyGrid): 2D OccupancyGrid Map
|
||||
|
||||
"""
|
||||
self.size_x = occupancy_map.info.width
|
||||
self.size_y = occupancy_map.info.height
|
||||
self.resolution = occupancy_map.info.resolution
|
||||
self.origin_x = occupancy_map.info.origin.position.x
|
||||
self.origin_y = occupancy_map.info.origin.position.y
|
||||
self.global_frame_id = occupancy_map.header.frame_id
|
||||
self.costmap_timestamp = occupancy_map.header.stamp
|
||||
# Extract costmap
|
||||
self.costmap = np.array(occupancy_map.data, dtype=np.uint8)
|
||||
|
||||
def getSizeInCellsX(self):
|
||||
"""Get map width in cells."""
|
||||
return self.size_x
|
||||
|
||||
def getSizeInCellsY(self):
|
||||
"""Get map height in cells."""
|
||||
return self.size_y
|
||||
|
||||
def getSizeInMetersX(self):
|
||||
"""Get x axis map size in meters."""
|
||||
return (self.size_x - 1 + 0.5) * self.resolution
|
||||
|
||||
def getSizeInMetersY(self):
|
||||
"""Get y axis map size in meters."""
|
||||
return (self.size_y - 1 + 0.5) * self.resolution
|
||||
|
||||
def getOriginX(self):
|
||||
"""Get the origin x axis of the map [m]."""
|
||||
return self.origin_x
|
||||
|
||||
def getOriginY(self):
|
||||
"""Get the origin y axis of the map [m]."""
|
||||
return self.origin_y
|
||||
|
||||
def getResolution(self):
|
||||
"""Get map resolution [m/cell]."""
|
||||
return self.resolution
|
||||
|
||||
def getGlobalFrameID(self):
|
||||
"""Get global frame_id."""
|
||||
return self.global_frame_id
|
||||
|
||||
def getCostmapTimestamp(self):
|
||||
"""Get costmap timestamp."""
|
||||
return self.costmap_timestamp
|
||||
|
||||
def getCostXY(self, mx: int, my: int) -> np.uint8:
|
||||
"""
|
||||
Get the cost of a cell in the costmap using map coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
mx (int): map coordinate X to get cost
|
||||
my (int): map coordinate Y to get cost
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.uint8: cost of a cell
|
||||
|
||||
"""
|
||||
return self.costmap[self.getIndex(mx, my)]
|
||||
|
||||
def getCostIdx(self, index: int) -> np.uint8:
|
||||
"""
|
||||
Get the cost of a cell in the costmap using Index.
|
||||
|
||||
Args
|
||||
----
|
||||
index (int): index of cell to get cost
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.uint8: cost of a cell
|
||||
|
||||
"""
|
||||
return self.costmap[index]
|
||||
|
||||
def setCost(self, mx: int, my: int, cost: np.uint8) -> None:
|
||||
"""
|
||||
Set the cost of a cell in the costmap using map coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
mx (int): map coordinate X to get cost
|
||||
my (int): map coordinate Y to get cost
|
||||
cost (np.uint8): The cost to set the cell
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
"""
|
||||
self.costmap[self.getIndex(mx, my)] = cost
|
||||
|
||||
def mapToWorld(self, mx: int, my: int) -> tuple[float, float]:
|
||||
"""
|
||||
Get the world coordinate XY using map coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
mx (int): map coordinate X to get world coordinate
|
||||
my (int): map coordinate Y to get world coordinate
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple of float: wx, wy
|
||||
wx (float) [m]: world coordinate X
|
||||
wy (float) [m]: world coordinate Y
|
||||
|
||||
"""
|
||||
wx = self.origin_x + (mx + 0.5) * self.resolution
|
||||
wy = self.origin_y + (my + 0.5) * self.resolution
|
||||
return (wx, wy)
|
||||
|
||||
def worldToMap(self, wx: float, wy: float) -> tuple[int, int]:
|
||||
"""
|
||||
Get the map coordinate XY using world coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
wx (float) [m]: world coordinate X to get map coordinate
|
||||
wy (float) [m]: world coordinate Y to get map coordinate
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple of int: mx, my
|
||||
mx (int): map coordinate X
|
||||
my (int): map coordinate Y
|
||||
|
||||
"""
|
||||
mx = int((wx - self.origin_x) // self.resolution)
|
||||
my = int((wy - self.origin_y) // self.resolution)
|
||||
return (mx, my)
|
||||
|
||||
def getIndex(self, mx: int, my: int) -> int:
|
||||
"""
|
||||
Get the index of the cell using map coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
mx (int): map coordinate X to get Index
|
||||
my (int): map coordinate Y to get Index
|
||||
|
||||
Returns
|
||||
-------
|
||||
int: The index of the cell
|
||||
|
||||
"""
|
||||
return my * self.size_x + mx
|
||||
@@ -0,0 +1,100 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
|
||||
"""
|
||||
Basic stock inspection demo. In this demonstration, the expectation
|
||||
is that there are cameras or RFID sensors mounted on the robots
|
||||
collecting information about stock quantity and location.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Inspection route, probably read in from a file for a real application
|
||||
# from either a map or drive and repeat.
|
||||
inspection_route = [
|
||||
[3.461, -0.450],
|
||||
[5.531, -0.450],
|
||||
[3.461, -2.200],
|
||||
[5.531, -2.200],
|
||||
[3.661, -4.121],
|
||||
[5.431, -4.121],
|
||||
[3.661, -5.850],
|
||||
[5.431, -5.800]]
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# Send our route
|
||||
inspection_points = []
|
||||
inspection_pose = PoseStamped()
|
||||
inspection_pose.header.frame_id = 'map'
|
||||
inspection_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
inspection_pose.pose.orientation.z = 1.0
|
||||
inspection_pose.pose.orientation.w = 0.0
|
||||
for pt in inspection_route:
|
||||
inspection_pose.pose.position.x = pt[0]
|
||||
inspection_pose.pose.position.y = pt[1]
|
||||
inspection_points.append(deepcopy(inspection_pose))
|
||||
navigator.followWaypoints(inspection_points)
|
||||
|
||||
# Do something during our route (e.x. AI to analyze stock information or upload to the cloud)
|
||||
# Simply the current waypoint ID for the demonstation
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Executing current waypoint: ' +
|
||||
str(feedback.current_waypoint + 1) + '/' + str(len(inspection_points)))
|
||||
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Inspection of shelves complete! Returning to start...')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Inspection of shelving was canceled. Returning to start...')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Inspection of shelving failed! Returning to start...')
|
||||
|
||||
# go back to start
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
navigator.goToPose(initial_pose)
|
||||
while not navigator.isTaskComplete():
|
||||
pass
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,123 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
|
||||
# Shelf positions for picking
|
||||
shelf_positions = {
|
||||
'shelf_A': [-3.829, -7.604],
|
||||
'shelf_B': [-3.791, -3.287],
|
||||
'shelf_C': [-3.791, 1.254],
|
||||
'shelf_D': [-3.24, 5.861]}
|
||||
|
||||
# Shipping destination for picked products
|
||||
shipping_destinations = {
|
||||
'recycling': [-0.205, 7.403],
|
||||
'pallet_jack7': [-0.073, -8.497],
|
||||
'conveyer_432': [6.217, 2.153],
|
||||
'frieght_bay_3': [-6.349, 9.147]}
|
||||
|
||||
"""
|
||||
Basic item picking demo. In this demonstration, the expectation
|
||||
is that there is a person at the item shelf to put the item on the robot
|
||||
and at the pallet jack to remove it
|
||||
(probably with some kind of button for 'got item, robot go do next task').
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
# Recieved virtual request for picking item at Shelf A and bring to
|
||||
# worker at the pallet jack 7 for shipping. This request would
|
||||
# contain the shelf ID ("shelf_A") and shipping destination ("pallet_jack7")
|
||||
####################
|
||||
request_item_location = 'shelf_C'
|
||||
request_destination = 'pallet_jack7'
|
||||
####################
|
||||
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
shelf_item_pose = PoseStamped()
|
||||
shelf_item_pose.header.frame_id = 'map'
|
||||
shelf_item_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
shelf_item_pose.pose.position.x = shelf_positions[request_item_location][0]
|
||||
shelf_item_pose.pose.position.y = shelf_positions[request_item_location][1]
|
||||
shelf_item_pose.pose.orientation.z = 1.0
|
||||
shelf_item_pose.pose.orientation.w = 0.0
|
||||
print(f'Received request for item picking at {request_item_location}.')
|
||||
navigator.goToPose(shelf_item_pose)
|
||||
|
||||
# Do something during our route
|
||||
# (e.x. queue up future tasks or detect person for fine-tuned positioning)
|
||||
# Simply print information for workers on the robot's ETA for the demonstation
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Estimated time of arrival at ' + request_item_location +
|
||||
' for worker: ' + '{0:.0f}'.format(
|
||||
Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9)
|
||||
+ ' seconds.')
|
||||
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Got product from ' + request_item_location +
|
||||
'! Bringing product to shipping destination (' + request_destination + ')...')
|
||||
shipping_destination = PoseStamped()
|
||||
shipping_destination.header.frame_id = 'map'
|
||||
shipping_destination.header.stamp = navigator.get_clock().now().to_msg()
|
||||
shipping_destination.pose.position.x = shipping_destinations[request_destination][0]
|
||||
shipping_destination.pose.position.y = shipping_destinations[request_destination][1]
|
||||
shipping_destination.pose.orientation.z = 1.0
|
||||
shipping_destination.pose.orientation.w = 0.0
|
||||
navigator.goToPose(shipping_destination)
|
||||
|
||||
elif result == TaskResult.CANCELED:
|
||||
print(f'Task at {request_item_location} was canceled. Returning to staging point...')
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
navigator.goToPose(initial_pose)
|
||||
|
||||
elif result == TaskResult.FAILED:
|
||||
print(f'Task at {request_item_location} failed!')
|
||||
exit(-1)
|
||||
|
||||
while not navigator.isTaskComplete():
|
||||
pass
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,105 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
|
||||
"""
|
||||
Basic recoveries demo. In this demonstration, the robot navigates
|
||||
to a dead-end where recoveries such as backup and spin are used
|
||||
to get out of it.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
goal_pose = PoseStamped()
|
||||
goal_pose.header.frame_id = 'map'
|
||||
goal_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose.pose.position.x = 6.13
|
||||
goal_pose.pose.position.y = 1.90
|
||||
goal_pose.pose.orientation.w = 1.0
|
||||
|
||||
navigator.goToPose(goal_pose)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print(
|
||||
f'Estimated time of arrival to destination is: \
|
||||
{Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9}'
|
||||
)
|
||||
|
||||
# Robot hit a dead end, back it up
|
||||
print('Robot hit a dead end, backing up...')
|
||||
navigator.backup(backup_dist=0.5, backup_speed=0.1)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print(f'Distance traveled: {feedback.distance_traveled}')
|
||||
|
||||
# Turn it around
|
||||
print('Spinning robot around...')
|
||||
navigator.spin(spin_dist=3.14)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print(f'Spin angle traveled: {feedback.angular_distance_traveled}')
|
||||
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Dead end confirmed! Returning to start...')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Recovery was canceled. Returning to start...')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Recovering from dead end failed! Returning to start...')
|
||||
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
navigator.goToPose(initial_pose)
|
||||
while not navigator.isTaskComplete():
|
||||
pass
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,107 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
|
||||
"""
|
||||
Basic security route patrol demo. In this demonstration, the expectation
|
||||
is that there are security cameras mounted on the robots recording or being
|
||||
watched live by security staff.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Security route, probably read in from a file for a real application
|
||||
# from either a map or drive and repeat.
|
||||
security_route = [
|
||||
[1.792, 2.144],
|
||||
[1.792, -5.44],
|
||||
[1.792, -9.427],
|
||||
[-3.665, -9.427],
|
||||
[-3.665, -4.303],
|
||||
[-3.665, 2.330],
|
||||
[-3.665, 9.283]]
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# Do security route until dead
|
||||
while rclpy.ok():
|
||||
# Send our route
|
||||
route_poses = []
|
||||
pose = PoseStamped()
|
||||
pose.header.frame_id = 'map'
|
||||
pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
pose.pose.orientation.w = 1.0
|
||||
for pt in security_route:
|
||||
pose.pose.position.x = pt[0]
|
||||
pose.pose.position.y = pt[1]
|
||||
route_poses.append(deepcopy(pose))
|
||||
navigator.goThroughPoses(route_poses)
|
||||
|
||||
# Do something during our route (e.x. AI detection on camera images for anomalies)
|
||||
# Simply print ETA for the demonstation
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Estimated time to complete current route: ' + '{0:.0f}'.format(
|
||||
Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9)
|
||||
+ ' seconds.')
|
||||
|
||||
# Some failure mode, must stop since the robot is clearly stuck
|
||||
if Duration.from_msg(feedback.navigation_time) > Duration(seconds=180.0):
|
||||
print('Navigation has exceeded timeout of 180s, canceling request.')
|
||||
navigator.cancelTask()
|
||||
|
||||
# If at end of route, reverse the route to restart
|
||||
security_route.reverse()
|
||||
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Route complete! Restarting...')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Security route was canceled, exiting.')
|
||||
exit(1)
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Security route failed! Restarting from other side...')
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,56 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2022 Joshua Wallace
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from time import sleep
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator
|
||||
import rclpy
|
||||
|
||||
"""
|
||||
Basic navigation demo to go to pose.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate, since autostarting nav2
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
navigator.assistedTeleop(time_allowance=20)
|
||||
while not navigator.isTaskComplete():
|
||||
# Publish twist commands to be filtered by the assisted teleop action
|
||||
sleep(0.2)
|
||||
pass
|
||||
|
||||
navigator.lifecycleShutdown()
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
|
||||
|
||||
"""
|
||||
Basic navigation demo to follow a given path after smoothing
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Wait for navigation to fully activate, since autostarting nav2
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# Go to our demos first goal pose
|
||||
goal_pose = PoseStamped()
|
||||
goal_pose.header.frame_id = 'map'
|
||||
goal_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose.pose.position.x = -3.0
|
||||
goal_pose.pose.position.y = -2.0
|
||||
goal_pose.pose.orientation.w = 1.0
|
||||
|
||||
# Get the path, smooth it
|
||||
path = navigator.getPath(initial_pose, goal_pose)
|
||||
smoothed_path = navigator.smoothPath(path)
|
||||
|
||||
# Follow path
|
||||
navigator.followPath(smoothed_path)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
################################################
|
||||
#
|
||||
# Implement some code here for your application!
|
||||
#
|
||||
################################################
|
||||
|
||||
# Do something with the feedback
|
||||
i += 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Estimated distance remaining to goal position: ' +
|
||||
'{0:.3f}'.format(feedback.distance_to_goal) +
|
||||
'\nCurrent speed of the robot: ' +
|
||||
'{0:.3f}'.format(feedback.speed))
|
||||
|
||||
# Do something depending on the return code
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Goal succeeded!')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Goal was canceled!')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Goal failed!')
|
||||
else:
|
||||
print('Goal has an invalid return status!')
|
||||
|
||||
navigator.lifecycleShutdown()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,139 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
"""
|
||||
Basic navigation demo to go to poses.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Activate navigation, if not autostarted. This should be called after setInitialPose()
|
||||
# or this will initialize at the origin of the map and update the costmap with bogus readings.
|
||||
# If autostart, you should `waitUntilNav2Active()` instead.
|
||||
# navigator.lifecycleStartup()
|
||||
|
||||
# Wait for navigation to fully activate, since autostarting nav2
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# If desired, you can change or load the map as well
|
||||
# navigator.changeMap('/path/to/map.yaml')
|
||||
|
||||
# You may use the navigator to clear or obtain costmaps
|
||||
# navigator.clearAllCostmaps() # also have clearLocalCostmap() and clearGlobalCostmap()
|
||||
# global_costmap = navigator.getGlobalCostmap()
|
||||
# local_costmap = navigator.getLocalCostmap()
|
||||
|
||||
# set our demo's goal poses
|
||||
goal_poses = []
|
||||
goal_pose1 = PoseStamped()
|
||||
goal_pose1.header.frame_id = 'map'
|
||||
goal_pose1.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose1.pose.position.x = 1.5
|
||||
goal_pose1.pose.position.y = 0.55
|
||||
goal_pose1.pose.orientation.w = 0.707
|
||||
goal_pose1.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose1)
|
||||
|
||||
# additional goals can be appended
|
||||
goal_pose2 = PoseStamped()
|
||||
goal_pose2.header.frame_id = 'map'
|
||||
goal_pose2.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose2.pose.position.x = 1.5
|
||||
goal_pose2.pose.position.y = -3.75
|
||||
goal_pose2.pose.orientation.w = 0.707
|
||||
goal_pose2.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose2)
|
||||
goal_pose3 = PoseStamped()
|
||||
goal_pose3.header.frame_id = 'map'
|
||||
goal_pose3.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose3.pose.position.x = -3.6
|
||||
goal_pose3.pose.position.y = -4.75
|
||||
goal_pose3.pose.orientation.w = 0.707
|
||||
goal_pose3.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose3)
|
||||
|
||||
# sanity check a valid path exists
|
||||
# path = navigator.getPathThroughPoses(initial_pose, goal_poses)
|
||||
|
||||
navigator.goThroughPoses(goal_poses)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
################################################
|
||||
#
|
||||
# Implement some code here for your application!
|
||||
#
|
||||
################################################
|
||||
|
||||
# Do something with the feedback
|
||||
i = i + 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Estimated time of arrival: ' + '{0:.0f}'.format(
|
||||
Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9)
|
||||
+ ' seconds.')
|
||||
|
||||
# Some navigation timeout to demo cancellation
|
||||
if Duration.from_msg(feedback.navigation_time) > Duration(seconds=600.0):
|
||||
navigator.cancelTask()
|
||||
|
||||
# Some navigation request change to demo preemption
|
||||
if Duration.from_msg(feedback.navigation_time) > Duration(seconds=35.0):
|
||||
goal_pose4 = PoseStamped()
|
||||
goal_pose4.header.frame_id = 'map'
|
||||
goal_pose4.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose4.pose.position.x = -5.0
|
||||
goal_pose4.pose.position.y = -4.75
|
||||
goal_pose4.pose.orientation.w = 0.707
|
||||
goal_pose4.pose.orientation.z = 0.707
|
||||
navigator.goThroughPoses([goal_pose4])
|
||||
|
||||
# Do something depending on the return code
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Goal succeeded!')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Goal was canceled!')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Goal failed!')
|
||||
else:
|
||||
print('Goal has an invalid return status!')
|
||||
|
||||
navigator.lifecycleShutdown()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,112 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
"""
|
||||
Basic navigation demo to go to pose.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Activate navigation, if not autostarted. This should be called after setInitialPose()
|
||||
# or this will initialize at the origin of the map and update the costmap with bogus readings.
|
||||
# If autostart, you should `waitUntilNav2Active()` instead.
|
||||
# navigator.lifecycleStartup()
|
||||
|
||||
# Wait for navigation to fully activate, since autostarting nav2
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# If desired, you can change or load the map as well
|
||||
# navigator.changeMap('/path/to/map.yaml')
|
||||
|
||||
# You may use the navigator to clear or obtain costmaps
|
||||
# navigator.clearAllCostmaps() # also have clearLocalCostmap() and clearGlobalCostmap()
|
||||
# global_costmap = navigator.getGlobalCostmap()
|
||||
# local_costmap = navigator.getLocalCostmap()
|
||||
|
||||
# Go to our demos first goal pose
|
||||
goal_pose = PoseStamped()
|
||||
goal_pose.header.frame_id = 'map'
|
||||
goal_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose.pose.position.x = -2.0
|
||||
goal_pose.pose.position.y = -0.5
|
||||
goal_pose.pose.orientation.w = 1.0
|
||||
|
||||
# sanity check a valid path exists
|
||||
# path = navigator.getPath(initial_pose, goal_pose)
|
||||
|
||||
navigator.goToPose(goal_pose)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
################################################
|
||||
#
|
||||
# Implement some code here for your application!
|
||||
#
|
||||
################################################
|
||||
|
||||
# Do something with the feedback
|
||||
i = i + 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Estimated time of arrival: ' + '{0:.0f}'.format(
|
||||
Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9)
|
||||
+ ' seconds.')
|
||||
|
||||
# Some navigation timeout to demo cancellation
|
||||
if Duration.from_msg(feedback.navigation_time) > Duration(seconds=600.0):
|
||||
navigator.cancelTask()
|
||||
|
||||
# Some navigation request change to demo preemption
|
||||
if Duration.from_msg(feedback.navigation_time) > Duration(seconds=18.0):
|
||||
goal_pose.pose.position.x = -3.0
|
||||
navigator.goToPose(goal_pose)
|
||||
|
||||
# Do something depending on the return code
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Goal succeeded!')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Goal was canceled!')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Goal failed!')
|
||||
else:
|
||||
print('Goal has an invalid return status!')
|
||||
|
||||
navigator.lifecycleShutdown()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,142 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
|
||||
import rclpy
|
||||
from rclpy.duration import Duration
|
||||
|
||||
"""
|
||||
Basic navigation demo to go to poses.
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
rclpy.init()
|
||||
|
||||
navigator = BasicNavigator()
|
||||
|
||||
# Set our demo's initial pose
|
||||
initial_pose = PoseStamped()
|
||||
initial_pose.header.frame_id = 'map'
|
||||
initial_pose.header.stamp = navigator.get_clock().now().to_msg()
|
||||
initial_pose.pose.position.x = 3.45
|
||||
initial_pose.pose.position.y = 2.15
|
||||
initial_pose.pose.orientation.z = 1.0
|
||||
initial_pose.pose.orientation.w = 0.0
|
||||
navigator.setInitialPose(initial_pose)
|
||||
|
||||
# Activate navigation, if not autostarted. This should be called after setInitialPose()
|
||||
# or this will initialize at the origin of the map and update the costmap with bogus readings.
|
||||
# If autostart, you should `waitUntilNav2Active()` instead.
|
||||
# navigator.lifecycleStartup()
|
||||
|
||||
# Wait for navigation to fully activate, since autostarting nav2
|
||||
navigator.waitUntilNav2Active()
|
||||
|
||||
# If desired, you can change or load the map as well
|
||||
# navigator.changeMap('/path/to/map.yaml')
|
||||
|
||||
# You may use the navigator to clear or obtain costmaps
|
||||
# navigator.clearAllCostmaps() # also have clearLocalCostmap() and clearGlobalCostmap()
|
||||
# global_costmap = navigator.getGlobalCostmap()
|
||||
# local_costmap = navigator.getLocalCostmap()
|
||||
|
||||
# set our demo's goal poses to follow
|
||||
goal_poses = []
|
||||
goal_pose1 = PoseStamped()
|
||||
goal_pose1.header.frame_id = 'map'
|
||||
goal_pose1.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose1.pose.position.x = 1.5
|
||||
goal_pose1.pose.position.y = 0.55
|
||||
goal_pose1.pose.orientation.w = 0.707
|
||||
goal_pose1.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose1)
|
||||
|
||||
# additional goals can be appended
|
||||
goal_pose2 = PoseStamped()
|
||||
goal_pose2.header.frame_id = 'map'
|
||||
goal_pose2.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose2.pose.position.x = 1.5
|
||||
goal_pose2.pose.position.y = -3.75
|
||||
goal_pose2.pose.orientation.w = 0.707
|
||||
goal_pose2.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose2)
|
||||
goal_pose3 = PoseStamped()
|
||||
goal_pose3.header.frame_id = 'map'
|
||||
goal_pose3.header.stamp = navigator.get_clock().now().to_msg()
|
||||
goal_pose3.pose.position.x = -3.6
|
||||
goal_pose3.pose.position.y = -4.75
|
||||
goal_pose3.pose.orientation.w = 0.707
|
||||
goal_pose3.pose.orientation.z = 0.707
|
||||
goal_poses.append(goal_pose3)
|
||||
|
||||
# sanity check a valid path exists
|
||||
# path = navigator.getPath(initial_pose, goal_pose1)
|
||||
|
||||
nav_start = navigator.get_clock().now()
|
||||
navigator.followWaypoints(goal_poses)
|
||||
|
||||
i = 0
|
||||
while not navigator.isTaskComplete():
|
||||
################################################
|
||||
#
|
||||
# Implement some code here for your application!
|
||||
#
|
||||
################################################
|
||||
|
||||
# Do something with the feedback
|
||||
i = i + 1
|
||||
feedback = navigator.getFeedback()
|
||||
if feedback and i % 5 == 0:
|
||||
print('Executing current waypoint: ' +
|
||||
str(feedback.current_waypoint + 1) + '/' + str(len(goal_poses)))
|
||||
now = navigator.get_clock().now()
|
||||
|
||||
# Some navigation timeout to demo cancellation
|
||||
if now - nav_start > Duration(seconds=600.0):
|
||||
navigator.cancelTask()
|
||||
|
||||
# Some follow waypoints request change to demo preemption
|
||||
if now - nav_start > Duration(seconds=35.0):
|
||||
goal_pose4 = PoseStamped()
|
||||
goal_pose4.header.frame_id = 'map'
|
||||
goal_pose4.header.stamp = now.to_msg()
|
||||
goal_pose4.pose.position.x = -5.0
|
||||
goal_pose4.pose.position.y = -4.75
|
||||
goal_pose4.pose.orientation.w = 0.707
|
||||
goal_pose4.pose.orientation.z = 0.707
|
||||
goal_poses = [goal_pose4]
|
||||
nav_start = now
|
||||
navigator.followWaypoints(goal_poses)
|
||||
|
||||
# Do something depending on the return code
|
||||
result = navigator.getResult()
|
||||
if result == TaskResult.SUCCEEDED:
|
||||
print('Goal succeeded!')
|
||||
elif result == TaskResult.CANCELED:
|
||||
print('Goal was canceled!')
|
||||
elif result == TaskResult.FAILED:
|
||||
print('Goal failed!')
|
||||
else:
|
||||
print('Goal has an invalid return status!')
|
||||
|
||||
navigator.lifecycleShutdown()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
# Copyright 2022 Afif Swaidan
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
This is a Python3 API for a Footprint Collision Checker.
|
||||
|
||||
It provides the needed methods to manipulate the coordinates
|
||||
and calculate the cost of a Footprint
|
||||
"""
|
||||
|
||||
from math import cos, sin
|
||||
|
||||
from geometry_msgs.msg import Point32, Polygon
|
||||
from nav2_simple_commander.costmap_2d import PyCostmap2D
|
||||
from nav2_simple_commander.line_iterator import LineIterator
|
||||
|
||||
NO_INFORMATION = 255
|
||||
LETHAL_OBSTACLE = 254
|
||||
INSCRIBED_INFLATED_OBSTACLE = 253
|
||||
MAX_NON_OBSTACLE = 252
|
||||
FREE_SPACE = 0
|
||||
|
||||
|
||||
class FootprintCollisionChecker:
|
||||
"""
|
||||
FootprintCollisionChecker.
|
||||
|
||||
FootprintCollisionChecker Class for getting the cost
|
||||
and checking the collisions of a Footprint
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the FootprintCollisionChecker Object."""
|
||||
self.costmap_ = None
|
||||
pass
|
||||
|
||||
def footprintCost(self, footprint: Polygon):
|
||||
"""
|
||||
Iterate over all the points in a footprint and check for collision.
|
||||
|
||||
Args
|
||||
----
|
||||
footprint (Polygon): The footprint to calculate the collision cost for
|
||||
|
||||
Returns
|
||||
-------
|
||||
LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
|
||||
footprint_cost (float): The maximum cost found in the footprint points
|
||||
|
||||
"""
|
||||
footprint_cost = 0.0
|
||||
x1 = 0.0
|
||||
y1 = 0.0
|
||||
|
||||
x0, y0 = self.worldToMapValidated(footprint.points[0].x, footprint.points[0].y)
|
||||
|
||||
if x0 is None or y0 is None:
|
||||
return LETHAL_OBSTACLE
|
||||
|
||||
xstart = x0
|
||||
ystart = y0
|
||||
|
||||
for i in range(len(footprint.points) - 1):
|
||||
x1, y1 = self.worldToMapValidated(
|
||||
footprint.points[i + 1].x, footprint.points[i + 1].y
|
||||
)
|
||||
|
||||
if x1 is None or y1 is None:
|
||||
return LETHAL_OBSTACLE
|
||||
|
||||
footprint_cost = max(float(self.lineCost(x0, x1, y0, y1)), footprint_cost)
|
||||
x0 = x1
|
||||
y0 = y1
|
||||
|
||||
if footprint_cost == LETHAL_OBSTACLE:
|
||||
return footprint_cost
|
||||
|
||||
return max(float(self.lineCost(xstart, x1, ystart, y1)), footprint_cost)
|
||||
|
||||
def lineCost(self, x0, x1, y0, y1, step_size=0.5):
|
||||
"""
|
||||
Iterate over all the points along a line and check for collision.
|
||||
|
||||
Args
|
||||
----
|
||||
x0 (float): Abscissa of the initial point in map coordinates
|
||||
y0 (float): Ordinate of the initial point in map coordinates
|
||||
x1 (float): Abscissa of the final point in map coordinates
|
||||
y1 (float): Ordinate of the final point in map coordinates
|
||||
step_size (float): Optional, Increments' resolution, defaults to 0.5
|
||||
|
||||
Returns
|
||||
-------
|
||||
LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
|
||||
line_cost (float): The maximum cost found in the line points
|
||||
|
||||
"""
|
||||
line_cost = 0.0
|
||||
point_cost = -1.0
|
||||
line_iterator = LineIterator(x0, y0, x1, y1, step_size)
|
||||
|
||||
while line_iterator.isValid():
|
||||
point_cost = self.pointCost(
|
||||
int(line_iterator.getX()), int(line_iterator.getY())
|
||||
)
|
||||
|
||||
if point_cost == LETHAL_OBSTACLE:
|
||||
return point_cost
|
||||
|
||||
if line_cost < point_cost:
|
||||
line_cost = point_cost
|
||||
|
||||
line_iterator.advance()
|
||||
|
||||
return line_cost
|
||||
|
||||
def worldToMapValidated(self, wx: float, wy: float):
|
||||
"""
|
||||
Get the map coordinate XY using world coordinate XY.
|
||||
|
||||
Args
|
||||
----
|
||||
wx (float): world coordinate X
|
||||
wy (float): world coordinate Y
|
||||
|
||||
Returns
|
||||
-------
|
||||
None: if coordinates are invalid
|
||||
tuple of int: mx, my (if coordinates are valid)
|
||||
mx (int): map coordinate X
|
||||
my (int): map coordinate Y
|
||||
|
||||
"""
|
||||
if self.costmap_ is None:
|
||||
raise ValueError(
|
||||
'Costmap not specified, use setCostmap to specify the costmap first'
|
||||
)
|
||||
return self.costmap_.worldToMap(wx, wy)
|
||||
|
||||
def pointCost(self, x: int, y: int):
|
||||
"""
|
||||
Get the cost of a point in the costmap using map coordinates XY.
|
||||
|
||||
Args
|
||||
----
|
||||
mx (int): map coordinate X
|
||||
my (int): map coordinate Y
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.uint8: cost of a point
|
||||
|
||||
"""
|
||||
if self.costmap_ is None:
|
||||
raise ValueError(
|
||||
'Costmap not specified, use setCostmap to specify the costmap first'
|
||||
)
|
||||
return self.costmap_.getCostXY(x, y)
|
||||
|
||||
def setCostmap(self, costmap: PyCostmap2D):
|
||||
"""
|
||||
Specify which costmap to use.
|
||||
|
||||
Args
|
||||
----
|
||||
costmap (PyCostmap2D): costmap to use in the object's methods
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
"""
|
||||
self.costmap_ = costmap
|
||||
return None
|
||||
|
||||
def footprintCostAtPose(self, x: float, y: float, theta: float, footprint: Polygon):
|
||||
"""
|
||||
Get the cost of a footprint at a specific Pose in map coordinates.
|
||||
|
||||
Args
|
||||
----
|
||||
x (float): map coordinate X
|
||||
y (float): map coordinate Y
|
||||
theta (float): absolute rotation angle of the footprint
|
||||
footprint (Polygon): the footprint to calculate its cost at the given Pose
|
||||
|
||||
Returns
|
||||
-------
|
||||
LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
|
||||
footprint_cost (float): The maximum cost found in the footprint points
|
||||
|
||||
"""
|
||||
cos_th = cos(theta)
|
||||
sin_th = sin(theta)
|
||||
oriented_footprint = Polygon()
|
||||
|
||||
for i in range(len(footprint.points)):
|
||||
new_pt = Point32()
|
||||
new_pt.x = x + (
|
||||
footprint.points[i].x * cos_th - footprint.points[i].y * sin_th
|
||||
)
|
||||
new_pt.y = y + (
|
||||
footprint.points[i].x * sin_th + footprint.points[i].y * cos_th
|
||||
)
|
||||
oriented_footprint.points.append(new_pt)
|
||||
|
||||
return self.footprintCost(oriented_footprint)
|
||||
@@ -0,0 +1,177 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
# Copyright 2022 Afif Swaidan
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
This is a Python3 API for a line iterator.
|
||||
|
||||
It provides the ability to iterate
|
||||
through the points of a line.
|
||||
"""
|
||||
|
||||
from cmath import sqrt
|
||||
|
||||
|
||||
class LineIterator():
|
||||
"""
|
||||
LineIterator.
|
||||
|
||||
LineIterator Python3 API for iterating along the points of a given line
|
||||
"""
|
||||
|
||||
def __init__(self, x0, y0, x1, y1, step_size=1.0):
|
||||
"""
|
||||
Initialize the LineIterator.
|
||||
|
||||
Args
|
||||
----
|
||||
x0 (float): Abscissa of the initial point
|
||||
y0 (float): Ordinate of the initial point
|
||||
x1 (float): Abscissa of the final point
|
||||
y1 (float): Ordinate of the final point
|
||||
step_size (float): Optional, Increments' resolution, defaults to 1
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError: When one (or more) of the inputs is not a number
|
||||
ValueError: When step_size is not a positive number
|
||||
|
||||
"""
|
||||
if type(x0) not in [int, float]:
|
||||
raise TypeError("x0 must be a number (int or float)")
|
||||
|
||||
if type(y0) not in [int, float]:
|
||||
raise TypeError("y0 must be a number (int or float)")
|
||||
|
||||
if type(x1) not in [int, float]:
|
||||
raise TypeError("x1 must be a number (int or float)")
|
||||
|
||||
if type(y1) not in [int, float]:
|
||||
raise TypeError("y1 must be a number (int or float)")
|
||||
|
||||
if type(step_size) not in [int, float]:
|
||||
raise TypeError("step_size must be a number (int or float)")
|
||||
|
||||
if step_size <= 0:
|
||||
raise ValueError("step_size must be a positive number")
|
||||
|
||||
self.x0_ = x0
|
||||
self.y0_ = y0
|
||||
self.x1_ = x1
|
||||
self.y1_ = y1
|
||||
self.x_ = x0
|
||||
self.y_ = y0
|
||||
self.step_size_ = step_size
|
||||
|
||||
if x1 != x0 and y1 != y0:
|
||||
self.valid_ = True
|
||||
self.m_ = (y1-y0)/(x1-x0)
|
||||
self.b_ = y1 - (self.m_*x1)
|
||||
elif x1 == x0 and y1 != y0:
|
||||
self.valid_ = True
|
||||
elif y1 == y1 and x1 != x0:
|
||||
self.valid_ = True
|
||||
self.m_ = (y1-y0)/(x1-x0)
|
||||
self.b_ = y1 - (self.m_*x1)
|
||||
else:
|
||||
self.valid_ = False
|
||||
raise ValueError(
|
||||
"Line has zero length (All 4 points have same coordinates)")
|
||||
|
||||
def isValid(self):
|
||||
"""Check if line is valid."""
|
||||
return self.valid_
|
||||
|
||||
def advance(self):
|
||||
"""Advance to the next point in the line."""
|
||||
if self.x1_ > self.x0_:
|
||||
if self.x_ < self.x1_:
|
||||
self.x_ = round(self.clamp(
|
||||
self.x_ + self.step_size_, self.x0_, self.x1_), 5)
|
||||
self.y_ = round(self.m_ * self.x_ + self.b_, 5)
|
||||
else:
|
||||
self.valid_ = False
|
||||
elif self.x1_ < self.x0_:
|
||||
if self.x_ > self.x1_:
|
||||
self.x_ = round(self.clamp(
|
||||
self.x_ - self.step_size_, self.x1_, self.x0_), 5)
|
||||
self.y_ = round(self.m_ * self.x_ + self.b_, 5)
|
||||
else:
|
||||
self.valid_ = False
|
||||
else:
|
||||
if self.y1_ > self.y0_:
|
||||
if self.y_ < self.y1_:
|
||||
self.y_ = round(self.clamp(
|
||||
self.y_ + self.step_size_, self.y0_, self.y1_), 5)
|
||||
else:
|
||||
self.valid_ = False
|
||||
elif self.y1_ < self.y0_:
|
||||
if self.y_ > self.y1_:
|
||||
self.y_ = round(self.clamp(
|
||||
self.y_ - self.step_size_, self.y1_, self.y0_), 5)
|
||||
else:
|
||||
self.valid_ = False
|
||||
else:
|
||||
self.valid_ = False
|
||||
|
||||
def getX(self):
|
||||
"""Get the abscissa of the current point."""
|
||||
return self.x_
|
||||
|
||||
def getY(self):
|
||||
"""Get the ordinate of the current point."""
|
||||
return self.y_
|
||||
|
||||
def getX0(self):
|
||||
"""Get the abscissa of the initial point."""
|
||||
return self.x0_
|
||||
|
||||
def getY0(self):
|
||||
"""Get the ordinate of the intial point."""
|
||||
return self.y0_
|
||||
|
||||
def getX1(self):
|
||||
"""Get the abscissa of the final point."""
|
||||
return self.x1_
|
||||
|
||||
def getY1(self):
|
||||
"""Get the ordinate of the final point."""
|
||||
return self.y1_
|
||||
|
||||
def get_line_length(self):
|
||||
"""Get the length of the line."""
|
||||
return sqrt(pow(self.x1_ - self.x0_, 2) + pow(self.y1_ - self.y0_, 2))
|
||||
|
||||
def clamp(self, n, min_n, max_n):
|
||||
"""
|
||||
Clamp n to be between min_n and max_n.
|
||||
|
||||
Args
|
||||
----
|
||||
n (float): input value
|
||||
min_n (float): minimum value
|
||||
max_n (float): maximum value
|
||||
|
||||
Returns
|
||||
-------
|
||||
n (float): input value clamped between given min and max
|
||||
|
||||
"""
|
||||
if n < min_n:
|
||||
return min_n
|
||||
elif n > max_n:
|
||||
return max_n
|
||||
else:
|
||||
return n
|
||||
@@ -0,0 +1,593 @@
|
||||
#! /usr/bin/env python3
|
||||
# Copyright 2021 Samsung Research America
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from enum import Enum
|
||||
import time
|
||||
|
||||
from action_msgs.msg import GoalStatus
|
||||
from builtin_interfaces.msg import Duration
|
||||
from geometry_msgs.msg import Point
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from geometry_msgs.msg import PoseWithCovarianceStamped
|
||||
from lifecycle_msgs.srv import GetState
|
||||
from nav2_msgs.action import AssistedTeleop, BackUp, Spin
|
||||
from nav2_msgs.action import ComputePathThroughPoses, ComputePathToPose
|
||||
from nav2_msgs.action import FollowPath, FollowWaypoints, NavigateThroughPoses, NavigateToPose
|
||||
from nav2_msgs.action import SmoothPath
|
||||
from nav2_msgs.srv import ClearEntireCostmap, GetCostmap, LoadMap, ManageLifecycleNodes
|
||||
|
||||
import rclpy
|
||||
from rclpy.action import ActionClient
|
||||
from rclpy.duration import Duration as rclpyDuration
|
||||
from rclpy.node import Node
|
||||
from rclpy.qos import QoSDurabilityPolicy, QoSHistoryPolicy
|
||||
from rclpy.qos import QoSProfile, QoSReliabilityPolicy
|
||||
|
||||
|
||||
class TaskResult(Enum):
|
||||
UNKNOWN = 0
|
||||
SUCCEEDED = 1
|
||||
CANCELED = 2
|
||||
FAILED = 3
|
||||
|
||||
|
||||
class BasicNavigator(Node):
|
||||
|
||||
def __init__(self, node_name='basic_navigator', namespace=''):
|
||||
super().__init__(node_name=node_name, namespace=namespace)
|
||||
self.initial_pose = PoseStamped()
|
||||
self.initial_pose.header.frame_id = 'map'
|
||||
self.goal_handle = None
|
||||
self.result_future = None
|
||||
self.feedback = None
|
||||
self.status = None
|
||||
|
||||
amcl_pose_qos = QoSProfile(
|
||||
durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
|
||||
reliability=QoSReliabilityPolicy.RELIABLE,
|
||||
history=QoSHistoryPolicy.KEEP_LAST,
|
||||
depth=1)
|
||||
|
||||
self.initial_pose_received = False
|
||||
self.nav_through_poses_client = ActionClient(self,
|
||||
NavigateThroughPoses,
|
||||
'navigate_through_poses')
|
||||
self.nav_to_pose_client = ActionClient(self, NavigateToPose, 'navigate_to_pose')
|
||||
self.follow_waypoints_client = ActionClient(self, FollowWaypoints, 'follow_waypoints')
|
||||
self.follow_path_client = ActionClient(self, FollowPath, 'follow_path')
|
||||
self.compute_path_to_pose_client = ActionClient(self, ComputePathToPose,
|
||||
'compute_path_to_pose')
|
||||
self.compute_path_through_poses_client = ActionClient(self, ComputePathThroughPoses,
|
||||
'compute_path_through_poses')
|
||||
self.smoother_client = ActionClient(self, SmoothPath, 'smooth_path')
|
||||
self.spin_client = ActionClient(self, Spin, 'spin')
|
||||
self.backup_client = ActionClient(self, BackUp, 'backup')
|
||||
self.assisted_teleop_client = ActionClient(self, AssistedTeleop, 'assisted_teleop')
|
||||
self.localization_pose_sub = self.create_subscription(PoseWithCovarianceStamped,
|
||||
'amcl_pose',
|
||||
self._amclPoseCallback,
|
||||
amcl_pose_qos)
|
||||
self.initial_pose_pub = self.create_publisher(PoseWithCovarianceStamped,
|
||||
'initialpose',
|
||||
10)
|
||||
self.change_maps_srv = self.create_client(LoadMap, 'map_server/load_map')
|
||||
self.clear_costmap_global_srv = self.create_client(
|
||||
ClearEntireCostmap, 'global_costmap/clear_entirely_global_costmap')
|
||||
self.clear_costmap_local_srv = self.create_client(
|
||||
ClearEntireCostmap, 'local_costmap/clear_entirely_local_costmap')
|
||||
self.get_costmap_global_srv = self.create_client(GetCostmap, 'global_costmap/get_costmap')
|
||||
self.get_costmap_local_srv = self.create_client(GetCostmap, 'local_costmap/get_costmap')
|
||||
|
||||
def destroyNode(self):
|
||||
self.destroy_node()
|
||||
|
||||
def destroy_node(self):
|
||||
self.nav_through_poses_client.destroy()
|
||||
self.nav_to_pose_client.destroy()
|
||||
self.follow_waypoints_client.destroy()
|
||||
self.follow_path_client.destroy()
|
||||
self.compute_path_to_pose_client.destroy()
|
||||
self.compute_path_through_poses_client.destroy()
|
||||
self.smoother_client.destroy()
|
||||
self.spin_client.destroy()
|
||||
self.backup_client.destroy()
|
||||
super().destroy_node()
|
||||
|
||||
def setInitialPose(self, initial_pose):
|
||||
"""Set the initial pose to the localization system."""
|
||||
self.initial_pose_received = False
|
||||
self.initial_pose = initial_pose
|
||||
self._setInitialPose()
|
||||
|
||||
def goThroughPoses(self, poses, behavior_tree=''):
|
||||
"""Send a `NavThroughPoses` action request."""
|
||||
self.debug("Waiting for 'NavigateThroughPoses' action server")
|
||||
while not self.nav_through_poses_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'NavigateThroughPoses' action server not available, waiting...")
|
||||
|
||||
goal_msg = NavigateThroughPoses.Goal()
|
||||
goal_msg.poses = poses
|
||||
goal_msg.behavior_tree = behavior_tree
|
||||
|
||||
self.info(f'Navigating with {len(goal_msg.poses)} goals....')
|
||||
send_goal_future = self.nav_through_poses_client.send_goal_async(goal_msg,
|
||||
self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error(f'Goal with {len(poses)} poses was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def goToPose(self, pose, behavior_tree=''):
|
||||
"""Send a `NavToPose` action request."""
|
||||
self.debug("Waiting for 'NavigateToPose' action server")
|
||||
while not self.nav_to_pose_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'NavigateToPose' action server not available, waiting...")
|
||||
|
||||
goal_msg = NavigateToPose.Goal()
|
||||
goal_msg.pose = pose
|
||||
goal_msg.behavior_tree = behavior_tree
|
||||
|
||||
self.info('Navigating to goal: ' + str(pose.pose.position.x) + ' ' +
|
||||
str(pose.pose.position.y) + '...')
|
||||
send_goal_future = self.nav_to_pose_client.send_goal_async(goal_msg,
|
||||
self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Goal to ' + str(pose.pose.position.x) + ' ' +
|
||||
str(pose.pose.position.y) + ' was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def followWaypoints(self, poses):
|
||||
"""Send a `FollowWaypoints` action request."""
|
||||
self.debug("Waiting for 'FollowWaypoints' action server")
|
||||
while not self.follow_waypoints_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'FollowWaypoints' action server not available, waiting...")
|
||||
|
||||
goal_msg = FollowWaypoints.Goal()
|
||||
goal_msg.poses = poses
|
||||
|
||||
self.info(f'Following {len(goal_msg.poses)} goals....')
|
||||
send_goal_future = self.follow_waypoints_client.send_goal_async(goal_msg,
|
||||
self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error(f'Following {len(poses)} waypoints request was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def spin(self, spin_dist=1.57, time_allowance=10):
|
||||
self.debug("Waiting for 'Spin' action server")
|
||||
while not self.spin_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'Spin' action server not available, waiting...")
|
||||
goal_msg = Spin.Goal()
|
||||
goal_msg.target_yaw = spin_dist
|
||||
goal_msg.time_allowance = Duration(sec=time_allowance)
|
||||
|
||||
self.info(f'Spinning to angle {goal_msg.target_yaw}....')
|
||||
send_goal_future = self.spin_client.send_goal_async(goal_msg, self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Spin request was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def backup(self, backup_dist=0.15, backup_speed=0.025, time_allowance=10):
|
||||
self.debug("Waiting for 'Backup' action server")
|
||||
while not self.backup_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'Backup' action server not available, waiting...")
|
||||
goal_msg = BackUp.Goal()
|
||||
goal_msg.target = Point(x=float(backup_dist))
|
||||
goal_msg.speed = backup_speed
|
||||
goal_msg.time_allowance = Duration(sec=time_allowance)
|
||||
|
||||
self.info(f'Backing up {goal_msg.target.x} m at {goal_msg.speed} m/s....')
|
||||
send_goal_future = self.backup_client.send_goal_async(goal_msg, self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Backup request was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def assistedTeleop(self, time_allowance=30):
|
||||
self.debug("Wainting for 'assisted_teleop' action server")
|
||||
while not self.assisted_teleop_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'assisted_teleop' action server not available, waiting...")
|
||||
goal_msg = AssistedTeleop.Goal()
|
||||
goal_msg.time_allowance = Duration(sec=time_allowance)
|
||||
|
||||
self.info("Running 'assisted_teleop'....")
|
||||
send_goal_future = \
|
||||
self.assisted_teleop_client.send_goal_async(goal_msg, self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Assisted Teleop request was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def followPath(self, path, controller_id='', goal_checker_id=''):
|
||||
"""Send a `FollowPath` action request."""
|
||||
self.debug("Waiting for 'FollowPath' action server")
|
||||
while not self.follow_path_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'FollowPath' action server not available, waiting...")
|
||||
|
||||
goal_msg = FollowPath.Goal()
|
||||
goal_msg.path = path
|
||||
goal_msg.controller_id = controller_id
|
||||
goal_msg.goal_checker_id = goal_checker_id
|
||||
|
||||
self.info('Executing path...')
|
||||
send_goal_future = self.follow_path_client.send_goal_async(goal_msg,
|
||||
self._feedbackCallback)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Follow path was rejected!')
|
||||
return False
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
return True
|
||||
|
||||
def cancelTask(self):
|
||||
"""Cancel pending task request of any type."""
|
||||
self.info('Canceling current task.')
|
||||
if self.result_future:
|
||||
future = self.goal_handle.cancel_goal_async()
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
return
|
||||
|
||||
def isTaskComplete(self):
|
||||
"""Check if the task request of any type is complete yet."""
|
||||
if not self.result_future:
|
||||
# task was cancelled or completed
|
||||
return True
|
||||
rclpy.spin_until_future_complete(self, self.result_future, timeout_sec=0.10)
|
||||
if self.result_future.result():
|
||||
self.status = self.result_future.result().status
|
||||
if self.status != GoalStatus.STATUS_SUCCEEDED:
|
||||
self.debug(f'Task with failed with status code: {self.status}')
|
||||
return True
|
||||
else:
|
||||
# Timed out, still processing, not complete yet
|
||||
return False
|
||||
|
||||
self.debug('Task succeeded!')
|
||||
return True
|
||||
|
||||
def getFeedback(self):
|
||||
"""Get the pending action feedback message."""
|
||||
return self.feedback
|
||||
|
||||
def getResult(self):
|
||||
"""Get the pending action result message."""
|
||||
if self.status == GoalStatus.STATUS_SUCCEEDED:
|
||||
return TaskResult.SUCCEEDED
|
||||
elif self.status == GoalStatus.STATUS_ABORTED:
|
||||
return TaskResult.FAILED
|
||||
elif self.status == GoalStatus.STATUS_CANCELED:
|
||||
return TaskResult.CANCELED
|
||||
else:
|
||||
return TaskResult.UNKNOWN
|
||||
|
||||
def waitUntilNav2Active(self, navigator='bt_navigator', localizer='amcl'):
|
||||
"""Block until the full navigation system is up and running."""
|
||||
self._waitForNodeToActivate(localizer)
|
||||
if localizer == 'amcl':
|
||||
self._waitForInitialPose()
|
||||
self._waitForNodeToActivate(navigator)
|
||||
self.info('Nav2 is ready for use!')
|
||||
return
|
||||
|
||||
def _getPathImpl(self, start, goal, planner_id='', use_start=False):
|
||||
"""
|
||||
Send a `ComputePathToPose` action request.
|
||||
|
||||
Internal implementation to get the full result, not just the path.
|
||||
"""
|
||||
self.debug("Waiting for 'ComputePathToPose' action server")
|
||||
while not self.compute_path_to_pose_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'ComputePathToPose' action server not available, waiting...")
|
||||
|
||||
goal_msg = ComputePathToPose.Goal()
|
||||
goal_msg.start = start
|
||||
goal_msg.goal = goal
|
||||
goal_msg.planner_id = planner_id
|
||||
goal_msg.use_start = use_start
|
||||
|
||||
self.info('Getting path...')
|
||||
send_goal_future = self.compute_path_to_pose_client.send_goal_async(goal_msg)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Get path was rejected!')
|
||||
return None
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
rclpy.spin_until_future_complete(self, self.result_future)
|
||||
self.status = self.result_future.result().status
|
||||
if self.status != GoalStatus.STATUS_SUCCEEDED:
|
||||
self.warn(f'Getting path failed with status code: {self.status}')
|
||||
return None
|
||||
|
||||
return self.result_future.result().result
|
||||
|
||||
def getPath(self, start, goal, planner_id='', use_start=False):
|
||||
"""Send a `ComputePathToPose` action request."""
|
||||
rtn = self._getPathImpl(start, goal, planner_id, use_start)
|
||||
if not rtn:
|
||||
return None
|
||||
else:
|
||||
return rtn.path
|
||||
|
||||
def getPathThroughPoses(self, start, goals, planner_id='', use_start=False):
|
||||
"""Send a `ComputePathThroughPoses` action request."""
|
||||
self.debug("Waiting for 'ComputePathThroughPoses' action server")
|
||||
while not self.compute_path_through_poses_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'ComputePathThroughPoses' action server not available, waiting...")
|
||||
|
||||
goal_msg = ComputePathThroughPoses.Goal()
|
||||
goal_msg.start = start
|
||||
goal_msg.goals = goals
|
||||
goal_msg.planner_id = planner_id
|
||||
goal_msg.use_start = use_start
|
||||
|
||||
self.info('Getting path...')
|
||||
send_goal_future = self.compute_path_through_poses_client.send_goal_async(goal_msg)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Get path was rejected!')
|
||||
return None
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
rclpy.spin_until_future_complete(self, self.result_future)
|
||||
self.status = self.result_future.result().status
|
||||
if self.status != GoalStatus.STATUS_SUCCEEDED:
|
||||
self.warn(f'Getting path failed with status code: {self.status}')
|
||||
return None
|
||||
|
||||
return self.result_future.result().result.path
|
||||
|
||||
def _smoothPathImpl(self, path, smoother_id='', max_duration=2.0, check_for_collision=False):
|
||||
"""
|
||||
Send a `SmoothPath` action request.
|
||||
|
||||
Internal implementation to get the full result, not just the path.
|
||||
"""
|
||||
self.debug("Waiting for 'SmoothPath' action server")
|
||||
while not self.smoother_client.wait_for_server(timeout_sec=1.0):
|
||||
self.info("'SmoothPath' action server not available, waiting...")
|
||||
|
||||
goal_msg = SmoothPath.Goal()
|
||||
goal_msg.path = path
|
||||
goal_msg.max_smoothing_duration = rclpyDuration(seconds=max_duration).to_msg()
|
||||
goal_msg.smoother_id = smoother_id
|
||||
goal_msg.check_for_collisions = check_for_collision
|
||||
|
||||
self.info('Smoothing path...')
|
||||
send_goal_future = self.smoother_client.send_goal_async(goal_msg)
|
||||
rclpy.spin_until_future_complete(self, send_goal_future)
|
||||
self.goal_handle = send_goal_future.result()
|
||||
|
||||
if not self.goal_handle.accepted:
|
||||
self.error('Smooth path was rejected!')
|
||||
return None
|
||||
|
||||
self.result_future = self.goal_handle.get_result_async()
|
||||
rclpy.spin_until_future_complete(self, self.result_future)
|
||||
self.status = self.result_future.result().status
|
||||
if self.status != GoalStatus.STATUS_SUCCEEDED:
|
||||
self.warn(f'Getting path failed with status code: {self.status}')
|
||||
return None
|
||||
|
||||
return self.result_future.result().result
|
||||
|
||||
def smoothPath(self, path, smoother_id='', max_duration=2.0, check_for_collision=False):
|
||||
"""Send a `SmoothPath` action request."""
|
||||
rtn = self._smoothPathImpl(
|
||||
path, smoother_id, max_duration, check_for_collision)
|
||||
if not rtn:
|
||||
return None
|
||||
else:
|
||||
return rtn.path
|
||||
|
||||
def changeMap(self, map_filepath):
|
||||
"""Change the current static map in the map server."""
|
||||
while not self.change_maps_srv.wait_for_service(timeout_sec=1.0):
|
||||
self.info('change map service not available, waiting...')
|
||||
req = LoadMap.Request()
|
||||
req.map_url = map_filepath
|
||||
future = self.change_maps_srv.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
status = future.result().result
|
||||
if status != LoadMap.Response().RESULT_SUCCESS:
|
||||
self.error('Change map request failed!')
|
||||
else:
|
||||
self.info('Change map request was successful!')
|
||||
return
|
||||
|
||||
def clearAllCostmaps(self):
|
||||
"""Clear all costmaps."""
|
||||
self.clearLocalCostmap()
|
||||
self.clearGlobalCostmap()
|
||||
return
|
||||
|
||||
def clearLocalCostmap(self):
|
||||
"""Clear local costmap."""
|
||||
while not self.clear_costmap_local_srv.wait_for_service(timeout_sec=1.0):
|
||||
self.info('Clear local costmaps service not available, waiting...')
|
||||
req = ClearEntireCostmap.Request()
|
||||
future = self.clear_costmap_local_srv.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
return
|
||||
|
||||
def clearGlobalCostmap(self):
|
||||
"""Clear global costmap."""
|
||||
while not self.clear_costmap_global_srv.wait_for_service(timeout_sec=1.0):
|
||||
self.info('Clear global costmaps service not available, waiting...')
|
||||
req = ClearEntireCostmap.Request()
|
||||
future = self.clear_costmap_global_srv.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
return
|
||||
|
||||
def getGlobalCostmap(self):
|
||||
"""Get the global costmap."""
|
||||
while not self.get_costmap_global_srv.wait_for_service(timeout_sec=1.0):
|
||||
self.info('Get global costmaps service not available, waiting...')
|
||||
req = GetCostmap.Request()
|
||||
future = self.get_costmap_global_srv.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
return future.result().map
|
||||
|
||||
def getLocalCostmap(self):
|
||||
"""Get the local costmap."""
|
||||
while not self.get_costmap_local_srv.wait_for_service(timeout_sec=1.0):
|
||||
self.info('Get local costmaps service not available, waiting...')
|
||||
req = GetCostmap.Request()
|
||||
future = self.get_costmap_local_srv.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
return future.result().map
|
||||
|
||||
def lifecycleStartup(self):
|
||||
"""Startup nav2 lifecycle system."""
|
||||
self.info('Starting up lifecycle nodes based on lifecycle_manager.')
|
||||
for srv_name, srv_type in self.get_service_names_and_types():
|
||||
if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
|
||||
self.info(f'Starting up {srv_name}')
|
||||
mgr_client = self.create_client(ManageLifecycleNodes, srv_name)
|
||||
while not mgr_client.wait_for_service(timeout_sec=1.0):
|
||||
self.info(f'{srv_name} service not available, waiting...')
|
||||
req = ManageLifecycleNodes.Request()
|
||||
req.command = ManageLifecycleNodes.Request().STARTUP
|
||||
future = mgr_client.call_async(req)
|
||||
|
||||
# starting up requires a full map->odom->base_link TF tree
|
||||
# so if we're not successful, try forwarding the initial pose
|
||||
while True:
|
||||
rclpy.spin_until_future_complete(self, future, timeout_sec=0.10)
|
||||
if not future:
|
||||
self._waitForInitialPose()
|
||||
else:
|
||||
break
|
||||
self.info('Nav2 is ready for use!')
|
||||
return
|
||||
|
||||
def lifecycleShutdown(self):
|
||||
"""Shutdown nav2 lifecycle system."""
|
||||
self.info('Shutting down lifecycle nodes based on lifecycle_manager.')
|
||||
for srv_name, srv_type in self.get_service_names_and_types():
|
||||
if srv_type[0] == 'nav2_msgs/srv/ManageLifecycleNodes':
|
||||
self.info(f'Shutting down {srv_name}')
|
||||
mgr_client = self.create_client(ManageLifecycleNodes, srv_name)
|
||||
while not mgr_client.wait_for_service(timeout_sec=1.0):
|
||||
self.info(f'{srv_name} service not available, waiting...')
|
||||
req = ManageLifecycleNodes.Request()
|
||||
req.command = ManageLifecycleNodes.Request().SHUTDOWN
|
||||
future = mgr_client.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
future.result()
|
||||
return
|
||||
|
||||
def _waitForNodeToActivate(self, node_name):
|
||||
# Waits for the node within the tester namespace to become active
|
||||
self.debug(f'Waiting for {node_name} to become active..')
|
||||
node_service = f'{node_name}/get_state'
|
||||
state_client = self.create_client(GetState, node_service)
|
||||
while not state_client.wait_for_service(timeout_sec=1.0):
|
||||
self.info(f'{node_service} service not available, waiting...')
|
||||
|
||||
req = GetState.Request()
|
||||
state = 'unknown'
|
||||
while state != 'active':
|
||||
self.debug(f'Getting {node_name} state...')
|
||||
future = state_client.call_async(req)
|
||||
rclpy.spin_until_future_complete(self, future)
|
||||
if future.result() is not None:
|
||||
state = future.result().current_state.label
|
||||
self.debug(f'Result of get_state: {state}')
|
||||
time.sleep(2)
|
||||
return
|
||||
|
||||
def _waitForInitialPose(self):
|
||||
while not self.initial_pose_received:
|
||||
self.info('Setting initial pose')
|
||||
self._setInitialPose()
|
||||
self.info('Waiting for amcl_pose to be received')
|
||||
rclpy.spin_once(self, timeout_sec=1.0)
|
||||
return
|
||||
|
||||
def _amclPoseCallback(self, msg):
|
||||
self.debug('Received amcl pose')
|
||||
self.initial_pose_received = True
|
||||
return
|
||||
|
||||
def _feedbackCallback(self, msg):
|
||||
self.debug('Received action feedback message')
|
||||
self.feedback = msg.feedback
|
||||
return
|
||||
|
||||
def _setInitialPose(self):
|
||||
msg = PoseWithCovarianceStamped()
|
||||
msg.pose.pose = self.initial_pose.pose
|
||||
msg.header.frame_id = self.initial_pose.header.frame_id
|
||||
msg.header.stamp = self.initial_pose.header.stamp
|
||||
self.info('Publishing Initial Pose')
|
||||
self.initial_pose_pub.publish(msg)
|
||||
return
|
||||
|
||||
def info(self, msg):
|
||||
self.get_logger().info(msg)
|
||||
return
|
||||
|
||||
def warn(self, msg):
|
||||
self.get_logger().warn(msg)
|
||||
return
|
||||
|
||||
def error(self, msg):
|
||||
self.get_logger().error(msg)
|
||||
return
|
||||
|
||||
def debug(self, msg):
|
||||
self.get_logger().debug(msg)
|
||||
return
|
||||
Reference in New Issue
Block a user