Spot ROS 2

repository·main·Indexed 43 days ago

https://github.com/rai-opensource/spot_ros2

A set of ROS 2 packages to interface with Boston Dynamics' Spot robot, bridging the Spot SDK to the ROS 2 ecosystem via topics, services, and actions. It includes a core driver stack (spot_driver, spot_msgs, spot_common), a ROS 2 control stack for joint-level control (spot_ros2_control, spot_hardware_interface, spot_controllers), and support for image publishing and point clouds.

Tokens
15K
Snippets
40
Records
70
Agent score
55%

What's inside spot_ros2

  1. Overview of spot_msgs interfaces

    main
    spot_msgs is a ROS 2 package that provides specialized interfaces designed for interacting with the spot_driver. It serves as a high-level abstraction layer built on top of bosdyn_msgs (which contains autogenerated ROS messages from the Spot SDK Protobuf definitions). Use spot_msgs when you need ROS 2 compatible messages that are specifically tailored for the Spot driver's operational workflows.
  2. Overview of Spot ROS 2 packages

    main

    The spot_ros2 repository contains several specialized packages:

    Core Driver Stack

    • spot_driver: Core driver for operating Spot (topics, services, actions).
    • spot_msgs: Custom messages, services, and interfaces.
    • spot_common: Common utilities.
    • spot_examples: Usage examples for the driver.

    ROS 2 Control Stack (Joint Level Control)

    • spot_ros2_control: Launchfiles for bringing up the ROS 2 control stack.
    • spot_hardware_interface: ROS 2 control hardware interface plugin for joint level API.
    • spot_controllers: Forwarding controller plugins.

    Submodules and Dependencies

    • ros_utilities: RAI Institute convenience wrappers for ROS 2.
    • spot_wrapper: Python wrapper around the Spot SDK.
    • spot_description: URDF and visualization launchfiles.
    • bosdyn_msgs: ROS versions of Boston Dynamics' protobufs (required).
  3. Configure Namespacing and TF Frames

    main

    Namespacing

    To avoid using the global namespace, use the spot_name argument or parameter. This places all driver-provided nodes, topics, services, and actions under the <Spot Name> namespace. It also prefixes all TF frames and joints with <Spot Name>.

    TF Frames

    • Root Frame: By default, the odom frame is the root of the TF tree. You can change this by setting the tf_root parameter to either vision or body (without a prefix).
    • Odometry Frame: By default, odometry is published relative to odom. To change this to vision, update the preferred_odom_frame parameter in your config file (without a prefix).
  4. Convert Spot SDK IK calls to ROS 2 service calls

    main

    When migrating from the Spot SDK to ROS 2, direct Protobuf-based API calls are replaced by ROS 2 service calls. The logic involves constructing an InverseKinematicsRequest (using Protobuf structures) and then converting that request into a ROS 2 service request for the GetInverseKinematicSolutions service.

    Workflow:

    1. Construct the InverseKinematicsRequest using Protobuf messages (e.g., scene_tform_task, wrist_mounted_tool, and tool_pose_task).
    2. Create a GetInverseKinematicSolutions.Request object.
    3. Use a convert function to map the Protobuf request into the ROS 2 request.
    4. Call the service via the ROS 2 client.
    5. Convert the resulting ROS 2 response back into a Protobuf InverseKinematicsResponse for easier handling.
        def _send_ik_request(
            self, odom_T_task: SE3Pose, wr1_T_tool: SE3Pose, task_T_desired_tool: SE3Pose
        ) -> GetInverseKinematicSolutions.Request:
            """
            Send an IK request for a given task frame, tool and tool desired pose.
            Returns:
                An IK solution, if any.
            """
            ik_request = inverse_kinematics_pb2.InverseKinematicsRequest(
                root_frame_name=ODOM_FRAME_NAME,
                scene_tform_task=odom_T_task.to_proto(),
                wrist_mounted_tool=inverse_kinematics_pb2.InverseKinematicsRequest.WristMountedTool(
                    wrist_tform_tool=wr1_T_tool.to_proto()
                ),
                tool_pose_task=inverse_kinematics_pb2.InverseKinematicsRequest.ToolPoseTask(
                    task_tform_desired_tool=task_T_desired_tool.to_proto()
                ),
            )
            request = GetInverseKinematicSolutions.Request()
            convert(ik_request, request.request)
            ik_reponse = self._ik_client.call(request)
    
            proto = inverse_kinematics_pb2.InverseKinematicsResponse()
            convert(ik_reponse.response, proto)
            return proto
  5. How the wasd interface commands the robot

    main

    The wasd example (implemented via the WasdInterface class) uses two primary ROS 2 communication patterns to control the robot:

    1. Service Calls for Discrete Actions

    Discrete actions like standing, sitting, or stowing the arm are performed by calling ROS 2 services. The example uses service clients for the Trigger message type.

    • Stand: calls the stand service.
    • Sit: calls the sit service.
    • Stow Arm: calls the arm_stow service.
    • Power: calls the power_on service.

    2. Topic Publishing for Locomotion

    Continuous movement (translation and rotation) is handled by publishing geometry_msgs/msg/Twist messages to the /cmd_vel topic.

    To prevent the robot from moving indefinitely, the example uses a helper function _velocity_cmd_helper() that:

    1. Constructs a Twist message with the requested linear (x, y) and angular (z) velocities.
    2. Continuously publishes the message for a set VELOCITY_CMD_DURATION.
    3. Publishes a zeroed-out Twist message (all fields at 0.0) once the duration expires to stop the robot.

    3. Subscriptions for State Monitoring

    The interface monitors robot state (such as battery levels) by subscribing to relevant topics. For example, it subscribes to /<robot_name>/status/battery_states using the BatteryStateArray message type to update the latest_battery_status member.

    # Example of how a service call is triggered via a keybind mapping
    self._command_dictionary = {
        ord("v"): self._sit,
    }
    
    # Example of the service call implementation
    def _sit(self) -> None:
        self.cli_sit.call_async(Trigger.Request())
    
    # Example of velocity command publishing pattern
    def _velocity_cmd_helper(self, desc: str = "", v_x: float = 0.0, v_y: float = 0.0, v_rot: float = 0.0) -> None:
        twist = Twist()
        twist.linear.x = v_x
        twist.linear.y = v_y
        twist.angular.z = v_rot
        start_time = time.time()
        while time.time() - start_time < VELOCITY_CMD_DURATION:
            self.pub_cmd_vel.publish(twist)
            time.sleep(0.01)
        self.pub_cmd_vel.publish(Twist())
  6. How command interfaces work in hello_spot

    main

    The hello_spot example utilizes two different interfaces to interact with the robot, depending on the complexity of the command:

    1. SimpleSpotCommander: Used for simple, high-level commands like claim, power_on, and stand. These are typically sent via the .command() method.
    2. ActionClientWrapper: Used for complex movements that require sending detailed goals (e.g., specific rotations or trajectories). It wraps the RobotCommand action server and provides a send_goal_and_wait method that handles the synchronous execution of the action.

    In the code, these are instantiated as:

    self.robot = SimpleSpotCommander(self._robot_name, node)
    self.robot_command_client = ActionClientWrapper(RobotCommand, 'robot_command', node)
  7. How spot_in_circle coordinates base movement and arm gaze

    main

    The SpotInCircle implementation coordinates Spot's base movement and arm gaze commands simultaneously using the synchros2 ROS 2 interface.

    Trajectory Logic

    The script uses get_me_a_circle(radius, steps) to approximate a circular path using a series of chords. Each segment consists of a forward motion (dx) and a fixed rotation (dyaw) relative to the robot's current position.

    Gaze Logic

    If an arm is detected and enabled, the script:

    1. Defines a gaze point (0, radius, 0.1) in the body frame.
    2. Transforms this gaze point to the odom frame using TFListenerWrapper.
    3. Sends an arm_gaze_command to keep the arm pointing at the center of the circle.

    Movement Execution

    The script iterates through the generated (dx, dy, dyaw) triplets, sending synchro_se2_trajectory_point_command for base movement at each step. After each movement segment, the robot is commanded to stop to ensure controlled transitions.

  8. Choose a ROS 2 controller for joint control

    main

    The package provides several controller options via the robot_controllers launch argument.

    Warning: For the forward_position_controller and forward_state_controller, the command array ordering must strictly match the configuration file. Incorrect ordering can cause dangerous, unpredictable robot motion.

    ControllerLaunch ArgumentCommand TopicExpected Command Format
    Forward Position (Default)(none)/<Robot Name>/forward_position_controller/commandsArray of positions (12 for no arm, 19 for arm)
    Forward Staterobot_controllers:=forward_state_controller/<Robot Name>/forward_state_controller/commands[positions, velocities, efforts] (36 for no arm, 57 for arm)
    Spot Joint Controllerrobot_controllers:=spot_joint_controller/<Robot Name>/spot_joint_controller/joint_commandsjoint_commands message (select specific joints by name; others remain unchanged)
  9. How to use subscriptions and actions for Spot pose commands

    main

    The simple_sub implementation demonstrates how to bridge ROS 2 topic subscriptions with robot actions. This pattern is useful when you want to send pose commands from a local planner to the Spot robot without losing commands during execution.

    Workflow

    1. Subscription: The script subscribes to the /pose_commands topic using the Pose message type.
    2. Buffering: To prevent command loss, the script maintains a latest_message buffer and an is_busy flag. If the robot is currently executing an action, incoming messages are stored in the buffer.
    3. Sequential Execution: When the robot is no longer busy, the script processes the buffered message. It uses robot_commander.walk_forward_with_vision_frame_goal(message) to convert the Pose message into a movement action.
    4. Completion: The script waits for each action to complete before resetting the is_busy flag and checking for the next buffered command, ensuring sequential movement.