Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| documentation:robots:spot:spot_ros [2023-04-24 21:23] – added spot_ros topics and usage examples Ming See | documentation:robots:spot:spot_ros [2023-04-28 11:41] (current) – [Further Reading and Resources] Ming See | ||
|---|---|---|---|
| Line 17: | Line 17: | ||
| | | ||
| Build the package with `catkin build`. | Build the package with `catkin build`. | ||
| - | cd ~/catkin_ws/src | + | cd ~/catkin_ws |
| catkin build | catkin build | ||
| | | ||
| Line 26: | Line 26: | ||
| Alternatively, | Alternatively, | ||
| + | - Download the image from Docker Hub with `sudo docker pull jeremysee/ | ||
| + | - Save the image into a .tar archive with `sudo docker save -o spotros.tar jeremysee/ | ||
| + | - Upload the .tar archive to Spot CORE using [[https:// | ||
| + | |||
| + | {{: | ||
| ===== Configuration ===== | ===== Configuration ===== | ||
| Line 91: | Line 96: | ||
| ===== ROS Usage ===== | ===== ROS Usage ===== | ||
| - | Here are some examples on how to make use of the spot_ros package in an external | + | The documentation |
| - | ==== Example 1 ==== | + | ===== ROS Services ===== |
| - | This was some example code used to traverse a GraphNav map and record fiducial positions along the way from the World Objects model of Spot. | + | The available ROS services |
| - | + | ||
| - | # | + | |
| - | import typing | + | |
| - | import pickle | + | |
| - | import time | + | |
| - | import rospy | + | |
| - | import actionlib | + | |
| - | from actionlib_msgs.msg import GoalStatus | + | |
| - | from rosservice import get_service_class_by_name | + | |
| - | from std_msgs.msg import String | + | |
| - | from geometry_msgs.msg import PoseStamped | + | |
| - | from spot_msgs.msg import WorldObjectArray, | + | |
| - | from spot_msgs.msg import AprilTagProperties | + | |
| - | from spot_msgs.msg import FrameTreeSnapshot, | + | |
| - | from spot_msgs.msg import NavigateInitAction, | + | |
| - | from spot_msgs.msg import NavigateToAction, | + | |
| - | from spot_msgs.srv import ListGraphResponse | + | |
| - | from std_srvs.srv import TriggerRequest | + | |
| - | from fiducial import Fiducial | + | |
| - | class SpotNav: | + | |
| - | def __init__(self): | + | |
| - | self.world_objects = None | + | |
| - | self.fiducials_seen: | + | |
| - | self.waypoint_fiducial: | + | |
| - | def initialize_subscribers(self): | + | |
| - | """ | + | |
| - | # Create a subscriber for the / | + | |
| - | self.world_objects_sub = rospy.Subscriber( | + | |
| - | "/ | + | |
| - | ) | + | |
| - | def initialize_publishers(self): | + | |
| - | """ | + | |
| - | self.reached_waypoint_pub = rospy.Publisher( | + | |
| - | "/ | + | |
| - | ) | + | |
| - | def initialize_action_clients(self): | + | |
| - | """ | + | |
| - | # Create an action client for the / | + | |
| - | self.navigate_to_client = actionlib.SimpleActionClient( | + | |
| - | "/ | + | |
| - | ) | + | |
| - | # Create an action client for the / | + | |
| - | self.navigate_init_client = actionlib.SimpleActionClient( | + | |
| - | "/ | + | |
| - | ) | + | |
| - | def world_objects_callback(self, | + | |
| - | # Save the message to a class variable tracking the fiducials in the message | + | |
| - | self.world_objects: | + | |
| - | # Iterate through the fiducials in the message, append the x,y,z coordinates to a dictionary | + | |
| - | for world_object in self.world_objects: | + | |
| - | april_tag: AprilTagProperties = world_object.apriltag_properties | + | |
| - | latest_snapshot: | + | |
| - | # Check if april_tag is None | + | |
| - | if april_tag is None: | + | |
| - | continue | + | |
| - | # Create the FrameTreeSnapshot as a dictionary | + | |
| - | frame_tree_snapshot: | + | |
| - | for child, parent_edge in zip( | + | |
| - | latest_snapshot.child_edges, | + | |
| - | ): | + | |
| - | parent_edge_transform = PoseStamped() | + | |
| - | parent_edge_transform.header.stamp = world_object.acquisition_time | + | |
| - | parent_edge_transform.header.frame_id = parent_edge.parent_frame_name | + | |
| - | parent_edge_transform.pose = parent_edge.parent_tform_child | + | |
| - | frame_tree_snapshot[child] = parent_edge_transform | + | |
| - | april_tag_pose = frame_tree_snapshot[f" | + | |
| - | april_tag_pose_filtered = frame_tree_snapshot[ | + | |
| - | f" | + | |
| - | ] | + | |
| - | # Build the april_tag into the Fiducial class | + | |
| - | fiducial = Fiducial( | + | |
| - | tag_id=april_tag.tag_id, | + | |
| - | dim_x=april_tag.x, | + | |
| - | dim_y=april_tag.y, | + | |
| - | fiducial_pose=april_tag_pose, | + | |
| - | filtered_fiducial_pose=april_tag_pose_filtered, | + | |
| - | pose_covariance=april_tag.detection_covariance, | + | |
| - | pose_covariance_frame=april_tag.detection_covariance_reference_frame, | + | |
| - | ) | + | |
| - | # Save the fiducial to the class variable | + | |
| - | if fiducial.tag_id in self.fiducials_seen: | + | |
| - | self.fiducials_seen[fiducial.tag_id].append(fiducial) | + | |
| - | else: | + | |
| - | self.fiducials_seen[fiducial.tag_id] = [fiducial] | + | |
| - | rospy.logdebug( | + | |
| - | f" | + | |
| - | ) | + | |
| - | def call_service(self, | + | |
| - | """ | + | |
| - | try: | + | |
| - | rospy.wait_for_service(service_name) | + | |
| - | service_type = get_service_class_by_name(service_name) | + | |
| - | proxy = rospy.ServiceProxy(service_name, | + | |
| - | return proxy(*args, | + | |
| - | except rospy.ServiceException as e: | + | |
| - | rospy.logerr(" | + | |
| - | def walk_current_graph(self): | + | |
| - | """ | + | |
| - | rospy.loginfo(" | + | |
| - | # Call the ListGraph service | + | |
| - | list_graph: ListGraphResponse = self.call_service("/ | + | |
| - | waypoints = list_graph.waypoint_ids | + | |
| - | # Call the / | + | |
| - | navigate_init_goal = NavigateInitGoal( | + | |
| - | upload_path="", | + | |
| - | initial_localization_fiducial=True, | + | |
| - | initial_localization_waypoint=" | + | |
| - | ) | + | |
| - | self.navigate_init_client.send_goal(navigate_init_goal) | + | |
| - | self.navigate_init_client.wait_for_result() | + | |
| - | # Check if the action succeeded | + | |
| - | if self.navigate_init_client.get_state() == GoalStatus.SUCCEEDED: | + | |
| - | rospy.loginfo(" | + | |
| - | for waypoint in waypoints: | + | |
| - | # Call the / | + | |
| - | navigate_to_goal = NavigateToGoal(navigate_to=waypoint) | + | |
| - | self.navigate_to_client.send_goal(navigate_to_goal) | + | |
| - | self.navigate_to_client.wait_for_result() | + | |
| - | # Check if the action succeeded | + | |
| - | if self.navigate_to_client.get_state() == GoalStatus.SUCCEEDED: | + | |
| - | rospy.loginfo(f" | + | |
| - | latest_world_objects_msg = rospy.wait_for_message( | + | |
| - | "/ | + | |
| - | ) | + | |
| - | latest_fiducial = self.extract_fiducials(latest_world_objects_msg) | + | |
| - | if waypoint in self.waypoint_fiducial: | + | |
| - | self.waypoint_fiducial[waypoint].append(latest_fiducial) | + | |
| - | else: | + | |
| - | self.waypoint_fiducial[waypoint] = [latest_fiducial] | + | |
| - | # Publish to the / | + | |
| - | self.reached_waypoint_pub.publish(waypoint) | + | |
| - | time.sleep(0.5) | + | |
| - | def extract_fiducials(self, | + | |
| - | self.world_objects: | + | |
| - | fiducial_list = [] | + | |
| - | # Iterate through the fiducials in the message, append the x,y,z coordinates to a dictionary | + | |
| - | for world_object in self.world_objects: | + | |
| - | april_tag: AprilTagProperties = world_object.apriltag_properties | + | |
| - | latest_snapshot: | + | |
| - | # Check if april_tag is None | + | |
| - | if april_tag is None: | + | |
| - | continue | + | |
| - | # Create the FrameTreeSnapshot as a dictionary | + | |
| - | frame_tree_snapshot: | + | |
| - | for child, parent_edge in zip( | + | |
| - | latest_snapshot.child_edges, | + | |
| - | ): | + | |
| - | parent_edge_transform = PoseStamped() | + | |
| - | parent_edge_transform.header.stamp = world_object.acquisition_time | + | |
| - | parent_edge_transform.header.frame_id = parent_edge.parent_frame_name | + | |
| - | parent_edge_transform.pose = parent_edge.parent_tform_child | + | |
| - | frame_tree_snapshot[child] = parent_edge_transform | + | |
| - | # Use tf2 to get the april_tag pose in the body frame | + | |
| - | april_tag_pose = frame_tree_snapshot[f" | + | |
| - | april_tag_pose_filtered = frame_tree_snapshot[ | + | |
| - | f" | + | |
| - | ] | + | |
| - | # Build the april_tag into the Fiducial class | + | |
| - | fiducial = Fiducial( | + | |
| - | tag_id=april_tag.tag_id, | + | |
| - | dim_x=april_tag.x, | + | |
| - | dim_y=april_tag.y, | + | |
| - | fiducial_pose=april_tag_pose, | + | |
| - | filtered_fiducial_pose=april_tag_pose_filtered, | + | |
| - | pose_covariance=april_tag.detection_covariance, | + | |
| - | pose_covariance_frame=april_tag.detection_covariance_reference_frame, | + | |
| - | ) | + | |
| - | # Append the fiducial to the list | + | |
| - | fiducial_list.append(fiducial) | + | |
| - | return fiducial_list | + | |
| - | def startup(self): | + | |
| - | rospy.loginfo(" | + | |
| - | # Call the / | + | |
| - | self.call_service("/ | + | |
| - | self.call_service("/ | + | |
| - | def shutdown(self): | + | |
| - | rospy.loginfo(" | + | |
| - | # Save the fiducials to a pickle file | + | |
| - | with open(f" | + | |
| - | pickle.dump(self.fiducials_seen, | + | |
| - | with open(f" | + | |
| - | pickle.dump(self.waypoint_fiducial, | + | |
| - | # Call the /spot/sit, / | + | |
| - | self.call_service("/ | + | |
| - | time.sleep(5) | + | |
| - | self.call_service("/ | + | |
| - | self.call_service("/ | + | |
| - | def main(self): | + | |
| - | rospy.init_node(" | + | |
| - | self.rates = rospy.get_param(" | + | |
| - | if " | + | |
| - | loop_rate = self.rates[" | + | |
| - | else: | + | |
| - | loop_rate = 50 | + | |
| - | # Initialize the node | + | |
| - | rate = rospy.Rate(loop_rate) | + | |
| - | rospy.loginfo(" | + | |
| - | self.initialize_subscribers() | + | |
| - | self.initialize_publishers() | + | |
| - | self.initialize_action_clients() | + | |
| - | rospy.on_shutdown(self.shutdown) | + | |
| - | # Walk the current graph | + | |
| - | self.startup() | + | |
| - | self.walk_current_graph() | + | |
| - | while not rospy.is_shutdown(): | + | |
| - | rate.sleep() | + | |
| - | if __name__ == " | + | |
| - | spot_nav = SpotNav() | + | |
| - | spot_nav.main() | + | |
| - | </ | + | |
| - | + | ||
| - | ===== ROS Topics ===== | + | |
| - | + | ||
| - | The available ROS topics | + | |
| ^ Topic ^ Type ^ Description | ^ Topic ^ Type ^ Description | ||
| Line 356: | Line 147: | ||
| | / | | / | ||
| | / | | / | ||
| + | |||
| + | ===== ROS Topics ===== | ||
| + | |||
| + | ^ Topic ^ Type ^ Description | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | /tf | tf2_msgs/ | ||
| + | | /tf_static | tf2_msgs/ | ||
| + | | / | ||
| + | | / | ||
| + | |||
| + | ===== ROS Actions ===== | ||
| + | |||
| + | ^ Topic ^ Type ^ Description ^ | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | / | ||
| + | | /spot/dock | DockAction | Dock or undock at a specific dock_id | | ||
| ===== Frequently Asked Questions (FAQ) ===== | ===== Frequently Asked Questions (FAQ) ===== | ||
| Line 372: | Line 245: | ||
| [[https:// | [[https:// | ||
| + | |||
| [[https:// | [[https:// | ||
| + | |||
| [[https:// | [[https:// | ||
| + | |||
| + | A snapshot of these repos are also available on the internal gitlab: | ||
| + | |||
| + | https:// | ||