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 19:23] – added spot_ros topics and usage examples mings | documentation:robots:spot:spot_ros [2023/04/28 09:41] (current) – [Further Reading and Resources] mings | ||
---|---|---|---|
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:// |