From 7eea51ccbf946eb20a5ae09013420626b30ad36e Mon Sep 17 00:00:00 2001
From: Katie Hughes
Date: Thu, 19 Dec 2024 18:20:21 -0500
Subject: [PATCH 01/23] Start revamping
Signed-off-by: Katie Hughes
---
README.md | 226 ++++++----------------------
spot_driver/EyeInHandCalibration.md | 86 +++++++++++
spot_driver/README.md | 68 ++++++++-
3 files changed, 191 insertions(+), 189 deletions(-)
create mode 100644 spot_driver/EyeInHandCalibration.md
diff --git a/README.md b/README.md
index a16733703..f29e18624 100644
--- a/README.md
+++ b/README.md
@@ -22,48 +22,52 @@
# Overview
-This is a ROS 2 package for Boston Dynamics' Spot. The package contains all necessary topics, services and actions to teleoperate or navigate Spot.
-This package is derived from this [ROS 1 package](https://github.com/heuristicus/spot_ros). This package currently corresponds to version 4.1.0 of the [spot-sdk](https://github.com/boston-dynamics/spot-sdk/releases/tag/v4.1.0).
+This repository contains a set of ROS 2 packages for interacting with Boston Dynamics' Spot.
+It is derived from [the ROS 1 equivalent](https://github.com/heuristicus/spot_ros).
+Currently, this repository corresponds to version 4.1.0 of the [spot-sdk](https://github.com/boston-dynamics/spot-sdk/releases/tag/v4.1.0).
-## Prerequisites
-This package is tested for Ubuntu 22.04 and ROS 2 Humble, which can be installed following [this guide](https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html).
+## Requirements
+This repository is supported for Ubuntu 22.04 and [ROS 2 Humble](https://docs.ros.org/en/humble/index.html) on both ARM64 and AMD64 platforms.
## Installation
-In your ROS 2 workspace `src` directory, clone the repository:
+Set up your ROS 2 workspace, and clone the repository in the `src` directory:
```bash
+mkdir -p /src && cd /src
git clone https://github.com/bdaiinstitute/spot_ros2.git
```
-and initialize and install the submodules:
+Then initialize and install the submodules.
```bash
cd spot_ros2
git submodule init
git submodule update
```
-Then run the install script to install the necessary Boston Dynamics and ROS dependencies. The install script takes the optional argument ```--arm64```; it otherwise defaults to an AMD64 install. Run the correct command based on your system:
+Next, run the following script to install the necessary Boston Dynamics and ROS dependencies.
+The install script takes the optional argument ```--arm64```; it otherwise defaults to an AMD64 install.
```bash
-cd
./install_spot_ros2.sh
or
./install_spot_ros2.sh --arm64
```
-From here, build and source the ROS 2 workspace:
-```
-cd
-source /opt/ros/humble/setup.bash
-colcon build --symlink-install --packages-ignore proto2ros_tests
-source install/local_setup.bash
+From here, build and source your ROS 2 workspace.
+```bash
+cd
+colcon build --symlink-install
+source install/setup.bash
```
-We suggest ignoring the `proto2ros_tests` package in the build as it is not necessary for running the driver. If you choose to build it, you will see a number of error messages from testing the failure paths.
-
### Alternative - Docker Image
-Alternatively, a Dockerfile is available that prepares a ready-to-run ROS2 Humble install with the Spot driver installed.
+Alternatively, a Dockerfile is available that prepares a ready-to-run ROS 2 Humble install with the Spot driver built.
-No special precautions or actions need to be taken to build the image. Just clone the repository and run `docker build` in the root of the repo to build the image.
+The Docker image can be built and run with the following commands:
+```bash
+cd /src/spot_ros2
+docker build . -t spot_ros2
+docker run -it spot_ros2:latest
+```
-No special actions need to be taken to run the image either. However, depending on what you intend to _do_ with the driver in your project, the following flags may be useful:
+The following flags may be useful for extra functionality when running the image:
| Flag | Use |
| -------- | --------------- |
@@ -71,171 +75,29 @@ No special actions need to be taken to run the image either. However, depending
| `-e DISPLAY` | Bind your display to the container in order to run GUI apps. Note that you will need to allow the Docker container to connect to your X11 server, which can be done in a number of ways ranging from disabling X11 authentication entirely, or by allowing the Docker daemon specifically to access your display server. |
| `--network host` | Use the host network directly. May help resolve issues connecting to Spot Wifi |
-The image does not have the `proto2ros_tests` package built. You'll need to build it yourself inside the container if you need to use it.
-
-# Spot ROS 2 Driver
-
-The Spot driver contains all of the necessary topics, services, and actions for controlling Spot over ROS 2. To launch the driver, run:
-```
-ros2 launch spot_driver spot_driver.launch.py [config_file:=] [spot_name:=] [publish_point_clouds:=] [launch_rviz:=] [uncompress_images:=] [publish_compressed_images:=]
-```
-
-## Configuration
-
-The Spot login data hostname, username and password can be specified either as ROS parameters or as environment variables. If using ROS parameters, see [`spot_driver/config/spot_ros_example.yaml`](spot_driver/config/spot_ros_example.yaml) for an example of what your file could look like. If using environment variables, define `BOSDYN_CLIENT_USERNAME`, `BOSDYN_CLIENT_PASSWORD`, and `SPOT_IP`.
-
-## Simple Robot Commands
-Many simple robot commands can be called as services from the command line once the driver is running. For example:
-
-* `ros2 service call //sit std_srvs/srv/Trigger`
-* `ros2 service call //stand std_srvs/srv/Trigger`
-* `ros2 service call //undock std_srvs/srv/Trigger`
-* `ros2 service call //power_off std_srvs/srv/Trigger`
-
-If your Spot has an arm, some additional helpful services are exposed:
-* `ros2 service call //arm_stow std_srvs/srv/Trigger`
-* `ros2 service call //arm_unstow std_srvs/srv/Trigger`
-* `ros2 service call //arm_carry std_srvs/srv/Trigger`
-* `ros2 service call //open_gripper std_srvs/srv/Trigger`
-* `ros2 service call //close_gripper std_srvs/srv/Trigger`
-
-The full list of interfaces provided by the driver can be explored via `ros2 topic list`, `ros2 service list`, and `ros2 action list`. For more information about the custom message types used in this package, run `ros2 interface show `.
-
-## Examples
-See [`spot_examples`](spot_examples/) for some more complex examples of using the ROS 2 driver to control Spot, which typically use the action servers provided by the driver.
-
-## Images
-Perception data from Spot is handled through the `spot_image_publishers.launch.py` launchfile, which is launched by default from the driver. If you want to only view images from Spot, without bringing up any of the nodes to control the robot, you can also choose to run this launchfile independently.
-
-By default, the driver will publish RGB images as well as depth maps from the `frontleft`, `frontright`, `left`, `right`, and `back` cameras on Spot (plus `hand` if your Spot has an arm). You can customize the cameras that are streamed from by adding the `cameras_used` parameter to your config yaml. (For example, to stream from only the front left and front right cameras, you can add `cameras_used: ["frontleft", "frontright"]`). Additionally, if your Spot has greyscale cameras, you will need to set `rgb_cameras: False` in your configuration YAML file, or you will not receive any image data.
-
-By default, the driver does not publish point clouds. To enable this, launch the driver with `publish_point_clouds:=True`.
-
-The driver can publish both compressed images (under `//camera//compressed`) and uncompressed images (under `//camera//image`). By default, it will only publish the uncompressed images. You can turn (un)compressed images on/off by launching the driver with the flags `uncompress_images:=` and `publish_compressed_images:=`.
-
-The driver also has the option to publish a stitched image created from Spot's front left and front right cameras (similar to what is seen on the tablet). If you wish to enable this, launch the driver with `stitch_front_images:=True`, and the image will be published under `//camera/frontmiddle_virtual/image`. In order to receive meaningful stitched images, you will have to specify the parameters `virtual_camera_intrinsics`, `virtual_camera_projection_plane`, `virtual_camera_plane_distance`, and `stitched_image_row_padding` (see [`spot_driver/config/spot_ros_example.yaml`](spot_driver/config/spot_ros_example.yaml) for some default values).
-
-> **_NOTE:_**
-If your image publishing rate is very slow, you can try
-> - connecting to your robot via ethernet cable
-> - exporting a custom DDS profile we have provided by running the following in the same terminal your driver will run in, or adding to your `.bashrc`:
-> ```
-> export=FASTRTPS_DEFAULT_PROFILES_FILE=/custom_dds_profile.xml
-> ```
-
-## Optional Automatic Eye-in-Hand Stereo Calibration Routine for Manipulator (Arm) Payload
-#### Collect Calibration
-An optional custom Automatic Eye-in-Hand Stereo Calibration Routine for the arm is available for use in the ```spot_wrapper``` submodule, where the
-output results can be used with ROS 2 for improved Depth to RGB correspondence for the hand cameras.
-See the readme at [```/spot_wrapper/spot_wrapper/calibration/README.md```](https://github.com/bdaiinstitute/spot_wrapper/tree/main/spot_wrapper/calibration/README.md) for
-target setup and relevant information.
-
-First, collect a calibration with ```spot_wrapper/spot_wrapper/calibrate_spot_hand_camera_cli.py```.
-Make sure to use the default ```--stereo_pairs``` configuration, and the default tag configuration (```--tag default```).
-
-For the robot and target setup described in [```/spot_wrapper/spot_wrapper/calibration/README.md```](https://github.com/bdaiinstitute/spot_wrapper/tree/main/spot_wrapper/calibration/README.md), the default viewpoint ranges should suffice.
-
-```
-python3 spot_wrapper/spot_wrapper/calibrate_spot_hand_camera_cli.py --ip -u user -pw --data_path ~/my_collection/ \
---save_data --result_path ~/my_collection/calibrated.yaml --photo_utilization_ratio 1 --stereo_pairs "[(1,0)]" \
---spot_rgb_photo_width=640 --spot_rgb_photo_height=480 --tag default --legacy_charuco_pattern True
-```
-
-Then, you can run a publisher to transform the depth image into the rgb images frame with the same image
-dimensions, so that finding the 3D location of a feature found in rgb can be as easy as passing
-the image feature pixel coordinates to the registered depth image, and extracting the 3D location.
-For all possible command line arguments, run ```ros2 run spot_driver calibated_reregistered_hand_camera_depth_publisher.py -h```
-
-#### Run the Calibrated Re-Publisher
-```
-ros2 run spot_driver calibrated_reregistered_hand_camera_depth_publisher.py --tag=default --calibration_path --robot_name --topic_name /depth_registered/hand_custom_cal/image
-```
-
-You can treat the reregistered topic, (in the above example, ```/depth_registered/hand_custom_cal/image```)
-as a drop in replacement by the registered image published by the default spot driver
-(```/depth_registered/hand/image```). The registered depth can be easily used in tools
-like downstream, like Open3d, (see [creating RGBD Images](https://www.open3d.org/docs/release/python_api/open3d.geometry.RGBDImage.html) and [creating color point clouds from RGBD Images](https://www.open3d.org/docs/release/python_api/open3d.geometry.PointCloud.html#open3d.geometry.PointCloud.create_from_rgbd_image)), due to matching image dimensions and registration
-to a shared frame.
-
-#### Comparing Calibration Results Quick Sanity Check
-You can compare the new calibration to the old calibration through comparing visualizing
-the colored point cloud from a bag in RViz. See RViz setup below the bagging instructions.
-
-
-First, collect a bag where there is a an object of a clearly different color in the foreground then
-that of the background.
-
-```
-ROBOT_NAME= && \
-ros2 bag record --output drop_in_test --topics /tf /tf_static \
-/${ROBOT_NAME}/depth/hand/image /${ROBOT_NAME}/camera/hand/camera_info \
-/${ROBOT_NAME}/joint_states /${ROBOT_NAME}/camera/hand/image \
-/${ROBOT_NAME}/depth_registered/hand/image
-```
-
-To see what the default calibration looks like:
-```
-# In seperate terminals
-
-ros2 bag play drop_in_test --loop
-ROBOT_NAME= && \
-ros2 launch spot_driver point_cloud_xyzrgb.launch.py spot_name:=${ROBOT_NAME} camera:=hand
-```
-
-To see what the new calibration looks like:
-```
-# In seperate terminals
-ROBOT_NAME= && \
-ros2 bag play drop_in_test --loop --topics /${ROBOT_NAME}/depth/hand/image \
-/${ROBOT_NAME}/camera/hand/camera_info /${ROBOT_NAME}/joint_states \
-/${ROBOT_NAME}/camera/hand/image /tf /tf_static
-
-ROBOT_NAME= && \
-CALIBRATION_PATH= && \
-ros2 run spot_driver calibrated_reregistered_hand_camera_depth_publisher.py --robot_name ${ROBOT_NAME} \
---calibration_path ${CALIBRATION_PATH} --topic depth_registered/hand/image
-
-ROBOT_NAME= && \
-ros2 launch spot_driver point_cloud_xyzrgb.launch.py spot_name:=${ROBOT_NAME} camera:=hand
-```
-
-#### RVIZ Setup for Sanity Check:
-Set global frame to be ```//hand```
-
-Add (bottom left) -> by topic ->
-```//depth_registered/hand/points``` -> ok
-
-On the left pane, expand the PointCloud2 message. Expand Topic. Set History
-Policy to be Keep Last, Reliability Policy to be Best Effort, and Durability policy to be
-Volatile (select these from the dropdowns).
-
-
-
-## Spot CAM
-Due to known issues with the Spot CAM, it is disabled by default. To enable publishing and usage over the driver, add the following command in your configuration YAML file:
- `initialize_spot_cam: True`
-
-The Spot CAM payload has known issues with the SSL certification process in https. If you get the following errors:
-
-```
-non-existing PPS 0 referenced
-decode_slice_header error
-no frame!
-```
-Then you want to log into the Spot CAM over the browser. In your browser, type in:
+# Packages
- https://:/h264.sdp.html
+This repo consists of a series of ROS 2 packages for usage with Spot. Further documentation on how each of these packages can be used can be found in their resepective README's.
-The default port for SDP is 31102 for the Spot CAM. Once inside, you will be prompted to log in using your username and password. Do so and the WebRTC frames should begin to properly stream.
+* `spot_description`: contains the URDF of Spot and some simple launchfiles for visualization.
+* `spot_driver`: Core driver for operating Spot. This contains all of the necessary topics, services, and actions for controlling Spot and receiving state information over ROS 2.
+* `spot_examples`: Examples of how to control Spot via the Spot driver.
+* `spot_msgs`: Custom messages, services, and interfaces relevant for operating Spot.
-# Spot ROS 2 Control
-This repository also provides a ROS 2 control package designed to interface with Spot's joint control API. Further documentation and examples can be found in [`spot_ros2_control`](./spot_ros2_control/).
+The following packages are used to enable joint level control of Spot via ROS 2 control.
+* `spot_ros2_control`: Contains core launchfiles for bringing up Spot's ROS 2 control stack, and some examples of how to use this.
+* `spot_hardware_interface`: Creates a ROS 2 control hardware interface plugin for operating Spot with the joint level API.
+* `spot_controllers`: Holds some simple forwarding controller plugins useful for sending commands.
-# Advanced Install
+This package also pulls in some relevant packages as submodules, listed below.
+* `ros_utilities`: The AI Institute's convenience wrappers around ROS 2.
+* `spot_wrapper`: A Python wrapper around the Spot SDK, shared as a common entry point with Spot's ROS 1 repo.
-## Install bosdyn_msgs from source
-The `bosdyn_msgs` package is installed as a debian package as part of the `install_spot_ros2` script because it's very large. It can be checked out from source [here](https://github.com/bdaiinstitute/bosdyn_msgs) and then built as a normal ROS 2 package if that is preferred (compilation takes about 15 minutes).
+This repository also depends on the `bosdyn_msgs` ROS package.
+This package contains ROS versions of [Boston Dynamics' protobufs](https://dev.bostondynamics.com/protos/bosdyn/api/proto_reference) that are used with the Spot SDK.
+As it is very large, this is installed as a debian package as part of `install_spot_ros2.sh`.
+It can be installed from source as a normal ROS package [here](https://github.com/bdaiinstitute/bosdyn_msgs) if desired instead.
# Help
@@ -295,8 +157,8 @@ pre-commit install
pre-commit run --all-files
```
-Now whenever you commit code to this repository, it will be checked against our `pre-commit` hooks. You can also run
-`git commit --no-verify` if you wish to commit without checking against the hooks.
+Now whenever you commit code to this repository, it will be checked against our `pre-commit` hooks.
+You can also run `git commit --no-verify` if you wish to commit without checking against the hooks.
## Contributors
@@ -310,7 +172,7 @@ MASKOR contributors:
* Stefan Schiffer
* Alexander Ferrein
-Boston Dynamics AI Institute contributors:
+AI Institute contributors:
* Jenny Barry
* Daniel Gonzalez
@@ -318,6 +180,8 @@ Boston Dynamics AI Institute contributors:
* David Surovik
* Jiuguang Wang
* David Watkins
+* Tiffany Cappellari
+* Katie Hughes
[Linköping University](https://liu.se/en/organisation/liu/ida) contributors:
diff --git a/spot_driver/EyeInHandCalibration.md b/spot_driver/EyeInHandCalibration.md
new file mode 100644
index 000000000..a02b31694
--- /dev/null
+++ b/spot_driver/EyeInHandCalibration.md
@@ -0,0 +1,86 @@
+
+## Optional Automatic Eye-in-Hand Stereo Calibration Routine for Manipulator (Arm) Payload
+#### Collect Calibration
+An optional custom Automatic Eye-in-Hand Stereo Calibration Routine for the arm is available for use in the ```spot_wrapper``` submodule, where the
+output results can be used with ROS 2 for improved Depth to RGB correspondence for the hand cameras.
+See the readme at [```/spot_wrapper/spot_wrapper/calibration/README.md```](https://github.com/bdaiinstitute/spot_wrapper/tree/main/spot_wrapper/calibration/README.md) for
+target setup and relevant information.
+
+First, collect a calibration with ```spot_wrapper/spot_wrapper/calibrate_spot_hand_camera_cli.py```.
+Make sure to use the default ```--stereo_pairs``` configuration, and the default tag configuration (```--tag default```).
+
+For the robot and target setup described in [```/spot_wrapper/spot_wrapper/calibration/README.md```](https://github.com/bdaiinstitute/spot_wrapper/tree/main/spot_wrapper/calibration/README.md), the default viewpoint ranges should suffice.
+
+```
+python3 spot_wrapper/spot_wrapper/calibrate_spot_hand_camera_cli.py --ip -u user -pw --data_path ~/my_collection/ \
+--save_data --result_path ~/my_collection/calibrated.yaml --photo_utilization_ratio 1 --stereo_pairs "[(1,0)]" \
+--spot_rgb_photo_width=640 --spot_rgb_photo_height=480 --tag default --legacy_charuco_pattern True
+```
+
+Then, you can run a publisher to transform the depth image into the rgb images frame with the same image
+dimensions, so that finding the 3D location of a feature found in rgb can be as easy as passing
+the image feature pixel coordinates to the registered depth image, and extracting the 3D location.
+For all possible command line arguments, run ```ros2 run spot_driver calibated_reregistered_hand_camera_depth_publisher.py -h```
+
+#### Run the Calibrated Re-Publisher
+```
+ros2 run spot_driver calibrated_reregistered_hand_camera_depth_publisher.py --tag=default --calibration_path --robot_name --topic_name /depth_registered/hand_custom_cal/image
+```
+
+You can treat the reregistered topic, (in the above example, ```/depth_registered/hand_custom_cal/image```)
+as a drop in replacement by the registered image published by the default spot driver
+(```/depth_registered/hand/image```). The registered depth can be easily used in tools
+like downstream, like Open3d, (see [creating RGBD Images](https://www.open3d.org/docs/release/python_api/open3d.geometry.RGBDImage.html) and [creating color point clouds from RGBD Images](https://www.open3d.org/docs/release/python_api/open3d.geometry.PointCloud.html#open3d.geometry.PointCloud.create_from_rgbd_image)), due to matching image dimensions and registration
+to a shared frame.
+
+#### Comparing Calibration Results Quick Sanity Check
+You can compare the new calibration to the old calibration through comparing visualizing
+the colored point cloud from a bag in RViz. See RViz setup below the bagging instructions.
+
+
+First, collect a bag where there is a an object of a clearly different color in the foreground then
+that of the background.
+
+```
+ROBOT_NAME= && \
+ros2 bag record --output drop_in_test --topics /tf /tf_static \
+/${ROBOT_NAME}/depth/hand/image /${ROBOT_NAME}/camera/hand/camera_info \
+/${ROBOT_NAME}/joint_states /${ROBOT_NAME}/camera/hand/image \
+/${ROBOT_NAME}/depth_registered/hand/image
+```
+
+To see what the default calibration looks like:
+```
+# In seperate terminals
+
+ros2 bag play drop_in_test --loop
+ROBOT_NAME= && \
+ros2 launch spot_driver point_cloud_xyzrgb.launch.py spot_name:=${ROBOT_NAME} camera:=hand
+```
+
+To see what the new calibration looks like:
+```
+# In seperate terminals
+ROBOT_NAME= && \
+ros2 bag play drop_in_test --loop --topics /${ROBOT_NAME}/depth/hand/image \
+/${ROBOT_NAME}/camera/hand/camera_info /${ROBOT_NAME}/joint_states \
+/${ROBOT_NAME}/camera/hand/image /tf /tf_static
+
+ROBOT_NAME= && \
+CALIBRATION_PATH= && \
+ros2 run spot_driver calibrated_reregistered_hand_camera_depth_publisher.py --robot_name ${ROBOT_NAME} \
+--calibration_path ${CALIBRATION_PATH} --topic depth_registered/hand/image
+
+ROBOT_NAME= && \
+ros2 launch spot_driver point_cloud_xyzrgb.launch.py spot_name:=${ROBOT_NAME} camera:=hand
+```
+
+#### RVIZ Setup for Sanity Check:
+Set global frame to be ```//hand```
+
+Add (bottom left) -> by topic ->
+```//depth_registered/hand/points``` -> ok
+
+On the left pane, expand the PointCloud2 message. Expand Topic. Set History
+Policy to be Keep Last, Reliability Policy to be Best Effort, and Durability policy to be
+Volatile (select these from the dropdowns).
diff --git a/spot_driver/README.md b/spot_driver/README.md
index 8d0cb5690..5fd8495cf 100644
--- a/spot_driver/README.md
+++ b/spot_driver/README.md
@@ -1,17 +1,69 @@
# spot_driver
-This package provides the central ROS 2 interface to interact with Spot.
+The Spot driver contains all of the necessary topics, services, and actions for controlling Spot over ROS 2. To launch the driver, run:
+```
+ros2 launch spot_driver spot_driver.launch.py [config_file:=] [spot_name:=] [publish_point_clouds:=] [launch_rviz:=] [uncompress_images:=] [publish_compressed_images:=]
+```
-The primary launchfile in this package is `spot_driver.launch.py`. Once you've built and sourced your workspace, launch it with:
+## Configuration
-`ros2 launch spot_driver spot_driver.launch.py`
+The Spot login data hostname, username and password can be specified either as ROS parameters or as environment variables. If using ROS parameters, see [`spot_driver/config/spot_ros_example.yaml`](spot_driver/config/spot_ros_example.yaml) for an example of what your file could look like. If using environment variables, define `BOSDYN_CLIENT_USERNAME`, `BOSDYN_CLIENT_PASSWORD`, and `SPOT_IP`.
-* Various driver parameters can be customized using a configuration file -- see [spot_ros_example.yaml](config/spot_ros_example.yaml) for reference. To launch the driver with a configuration file, add the launch argument `config_file:=path/to/config.yaml`. Note that you can specify the hostname, username, and password for login to the robot in this file, or they can alternatively be set in the environment variables `SPOT_IP`, `BOSDYN_CLIENT_USERNAME`, and `BOSDYN_CLIENT_PASSWORD`, respectively.
-* To launch the process within a namespace, add the launch argument `spot_name:={name}`
-* To visualize Spot in RViz, add the launch argument `launch_rviz:=True`. This will automatically generate the appropriate RViz config file for your robot's name using `rviz.launch.py`.
-* To publish point clouds, add the launch argument `publish_point_clouds:=True`. This is disabled by default.
+## Simple Robot Commands
+Many simple robot commands can be called as services from the command line once the driver is running. For example:
+
+* `ros2 service call //sit std_srvs/srv/Trigger`
+* `ros2 service call //stand std_srvs/srv/Trigger`
+* `ros2 service call //undock std_srvs/srv/Trigger`
+* `ros2 service call //power_off std_srvs/srv/Trigger`
+
+If your Spot has an arm, some additional helpful services are exposed:
+* `ros2 service call //arm_stow std_srvs/srv/Trigger`
+* `ros2 service call //arm_unstow std_srvs/srv/Trigger`
+* `ros2 service call //arm_carry std_srvs/srv/Trigger`
+* `ros2 service call //open_gripper std_srvs/srv/Trigger`
+* `ros2 service call //close_gripper std_srvs/srv/Trigger`
+
+The full list of interfaces provided by the driver can be explored via `ros2 topic list`, `ros2 service list`, and `ros2 action list`. For more information about the custom message types used in this package, run `ros2 interface show `.
+
+
+## Images
+Perception data from Spot is handled through the `spot_image_publishers.launch.py` launchfile, which is launched by default from the driver. If you want to only view images from Spot, without bringing up any of the nodes to control the robot, you can also choose to run this launchfile independently.
+
+By default, the driver will publish RGB images as well as depth maps from the `frontleft`, `frontright`, `left`, `right`, and `back` cameras on Spot (plus `hand` if your Spot has an arm). You can customize the cameras that are streamed from by adding the `cameras_used` parameter to your config yaml. (For example, to stream from only the front left and front right cameras, you can add `cameras_used: ["frontleft", "frontright"]`). Additionally, if your Spot has greyscale cameras, you will need to set `rgb_cameras: False` in your configuration YAML file, or you will not receive any image data.
+
+By default, the driver does not publish point clouds. To enable this, launch the driver with `publish_point_clouds:=True`.
+
+The driver can publish both compressed images (under `//camera//compressed`) and uncompressed images (under `//camera//image`). By default, it will only publish the uncompressed images. You can turn (un)compressed images on/off by launching the driver with the flags `uncompress_images:=` and `publish_compressed_images:=`.
+
+The driver also has the option to publish a stitched image created from Spot's front left and front right cameras (similar to what is seen on the tablet). If you wish to enable this, launch the driver with `stitch_front_images:=True`, and the image will be published under `//camera/frontmiddle_virtual/image`. In order to receive meaningful stitched images, you will have to specify the parameters `virtual_camera_intrinsics`, `virtual_camera_projection_plane`, `virtual_camera_plane_distance`, and `stitched_image_row_padding` (see [`spot_driver/config/spot_ros_example.yaml`](spot_driver/config/spot_ros_example.yaml) for some default values).
+
+> **_NOTE:_**
+If your image publishing rate is very slow, you can try
+> - connecting to your robot via ethernet cable
+> - exporting a custom DDS profile we have provided by running the following in the same terminal your driver will run in, or adding to your `.bashrc`:
+> ```
+> export=FASTRTPS_DEFAULT_PROFILES_FILE=/custom_dds_profile.xml
+> ```
+
+## Spot CAM
+Due to known issues with the Spot CAM, it is disabled by default. To enable publishing and usage over the driver, add the following command in your configuration YAML file:
+ `initialize_spot_cam: True`
+
+The Spot CAM payload has known issues with the SSL certification process in https. If you get the following errors:
+
+```
+non-existing PPS 0 referenced
+decode_slice_header error
+no frame!
+```
+
+Then you want to log into the Spot CAM over the browser. In your browser, type in:
+
+ https://:/h264.sdp.html
+
+The default port for SDP is 31102 for the Spot CAM. Once inside, you will be prompted to log in using your username and password. Do so and the WebRTC frames should begin to properly stream.
-The Spot driver contains both Python and C++ nodes. Spot's Python SDK is used for many operations. For example, `spot_ros2` is the primary node that connects with Spot and creates the ROS 2 action servers and services. Spot's C++ SDK is used in nodes like `spot_image_publisher_node` to retrieve images from Spot's RGB and depth cameras at close to their native refresh rate of 15 Hz -- something that is not possible using the Python SDK.
## Examples
For some examples of using the Spot ROS 2 driver, check out [`spot_examples`](../spot_examples/).
From 9690438012e2df9d75efebd03ccd09400ff0e4aa Mon Sep 17 00:00:00 2001
From: Katie Hughes
Date: Thu, 19 Dec 2024 18:27:40 -0500
Subject: [PATCH 02/23] More revamp
Signed-off-by: Katie Hughes
---
README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index f29e18624..dec07a496 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,11 @@
# Overview
-This repository contains a set of ROS 2 packages for interacting with Boston Dynamics' Spot.
-It is derived from [the ROS 1 equivalent](https://github.com/heuristicus/spot_ros).
+`spot_ros2` is a set of ROS 2 packages for interacting with Boston Dynamics' Spot, based off the [the ROS 1 equivalent](https://github.com/heuristicus/spot_ros).
Currently, this repository corresponds to version 4.1.0 of the [spot-sdk](https://github.com/boston-dynamics/spot-sdk/releases/tag/v4.1.0).
## Requirements
-This repository is supported for Ubuntu 22.04 and [ROS 2 Humble](https://docs.ros.org/en/humble/index.html) on both ARM64 and AMD64 platforms.
+This repository is supported for use on Ubuntu 22.04 and [ROS 2 Humble](https://docs.ros.org/en/humble/index.html) on both ARM64 and AMD64 platforms.
## Installation
Set up your ROS 2 workspace, and clone the repository in the `src` directory:
@@ -60,7 +59,7 @@ source install/setup.bash
Alternatively, a Dockerfile is available that prepares a ready-to-run ROS 2 Humble install with the Spot driver built.
-The Docker image can be built and run with the following commands:
+The Docker image can be built and minimally run with the following commands:
```bash
cd /src/spot_ros2
docker build . -t spot_ros2
From c76b3f8bffe95359d6bd86be98f27842da20d681 Mon Sep 17 00:00:00 2001
From: Katie Hughes
Date: Thu, 19 Dec 2024 18:50:20 -0500
Subject: [PATCH 03/23] more
Signed-off-by: Katie Hughes
---
README.md | 34 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index dec07a496..9b859fac6 100644
--- a/README.md
+++ b/README.md
@@ -23,10 +23,11 @@
# Overview
`spot_ros2` is a set of ROS 2 packages for interacting with Boston Dynamics' Spot, based off the [the ROS 1 equivalent](https://github.com/heuristicus/spot_ros).
+Its core [`spot_driver`](spot_driver) package exposes topics, services, and actions necessary to control Spot and send state information (such as images) back to the user.
Currently, this repository corresponds to version 4.1.0 of the [spot-sdk](https://github.com/boston-dynamics/spot-sdk/releases/tag/v4.1.0).
## Requirements
-This repository is supported for use on Ubuntu 22.04 and [ROS 2 Humble](https://docs.ros.org/en/humble/index.html) on both ARM64 and AMD64 platforms.
+This repository is supported for use with Ubuntu 22.04 and [ROS 2 Humble](https://docs.ros.org/en/humble/index.html) on both ARM64 and AMD64 platforms.
## Installation
Set up your ROS 2 workspace, and clone the repository in the `src` directory:
@@ -57,8 +58,7 @@ source install/setup.bash
### Alternative - Docker Image
-Alternatively, a Dockerfile is available that prepares a ready-to-run ROS 2 Humble install with the Spot driver built.
-
+A Dockerfile is available that prepares a ready-to-run ROS 2 Humble install with the Spot driver built.
The Docker image can be built and minimally run with the following commands:
```bash
cd /src/spot_ros2
@@ -66,8 +66,7 @@ docker build . -t spot_ros2
docker run -it spot_ros2:latest
```
-The following flags may be useful for extra functionality when running the image:
-
+The following flags may be useful for extra functionality when running the image.
| Flag | Use |
| -------- | --------------- |
| `--runtime nvidia` + `--gpus all` | Use the [NVIDIA Container Runtime](https://developer.nvidia.com/container-runtime) to run the container with GPU acceleration |
@@ -79,19 +78,19 @@ The following flags may be useful for extra functionality when running the image
This repo consists of a series of ROS 2 packages for usage with Spot. Further documentation on how each of these packages can be used can be found in their resepective README's.
-* `spot_description`: contains the URDF of Spot and some simple launchfiles for visualization.
-* `spot_driver`: Core driver for operating Spot. This contains all of the necessary topics, services, and actions for controlling Spot and receiving state information over ROS 2.
-* `spot_examples`: Examples of how to control Spot via the Spot driver.
-* `spot_msgs`: Custom messages, services, and interfaces relevant for operating Spot.
+* [`spot_description`](spot_description): contains the URDF of Spot and some simple launchfiles for visualization.
+* [`spot_driver`](spot_driver): Core driver for operating Spot. This contains all of the necessary topics, services, and actions for controlling Spot and receiving state information over ROS 2.
+* [`spot_examples`](spot_examples): Examples of how to control Spot via the Spot driver.
+* [`spot_msgs`](spot_msgs): Custom messages, services, and interfaces relevant for operating Spot.
The following packages are used to enable joint level control of Spot via ROS 2 control.
-* `spot_ros2_control`: Contains core launchfiles for bringing up Spot's ROS 2 control stack, and some examples of how to use this.
-* `spot_hardware_interface`: Creates a ROS 2 control hardware interface plugin for operating Spot with the joint level API.
-* `spot_controllers`: Holds some simple forwarding controller plugins useful for sending commands.
+* [`spot_ros2_control`](spot_ros2_control): Contains core launchfiles for bringing up Spot's ROS 2 control stack, and some examples of how to use this.
+* [`spot_hardware_interface`](spot_hardware_interface): Creates a ROS 2 control hardware interface plugin for operating Spot with the joint level API.
+* [`spot_controllers`](spot_controllers): Holds some simple forwarding controller plugins useful for sending commands.
This package also pulls in some relevant packages as submodules, listed below.
-* `ros_utilities`: The AI Institute's convenience wrappers around ROS 2.
-* `spot_wrapper`: A Python wrapper around the Spot SDK, shared as a common entry point with Spot's ROS 1 repo.
+* [`ros_utilities`](https://github.com/bdaiinstitute/ros_utilities): The AI Institute's convenience wrappers around ROS 2.
+* [`spot_wrapper`](https://github.com/bdaiinstitute/spot_wrapper): A Python wrapper around the Spot SDK, shared as a common entry point with Spot's ROS 1 repo.
This repository also depends on the `bosdyn_msgs` ROS package.
This package contains ROS versions of [Boston Dynamics' protobufs](https://dev.bostondynamics.com/protos/bosdyn/api/proto_reference) that are used with the Spot SDK.
@@ -101,7 +100,7 @@ It can be installed from source as a normal ROS package [here](https://github.co
# Help
-If you encounter problems when using this repository, feel free to open an issue or PR.
+If you encounter problems when using this repository, feel free to open an [issue](https://github.com/bdaiinstitute/spot_ros2/issues) or ask a question in the [discussions](https://github.com/bdaiinstitute/spot_ros2/discussions).
## Verify Package Versions
If you encounter `ModuleNotFoundErrors` with `bosdyn` packages upon running the driver, it is likely that the necessary Boston Dynamics API packages did not get installed with `install_spot_ros2.sh`. To check this, you can run the following command. Note that all versions should be `4.1.0`.
@@ -149,8 +148,7 @@ BSD3 license - parts of the code derived from the Clearpath Robotics ROS 1 drive
To contribute, install `pre-commit` via pip, run `pre-commit install` and then run `pre-commit run --all-files` to
verify that your code will pass inspection.
```bash
-git clone https://github.com/bdaiinstitute/spot_ros2.git
-cd spot_ros2
+cd /src/spot_ros2
pip3 install pre-commit
pre-commit install
pre-commit run --all-files
@@ -161,7 +159,7 @@ You can also run `git commit --no-verify` if you wish to commit without checking
## Contributors
-This project is a collaboration between the [Mobile Autonomous Systems & Cognitive Robotics Institute](https://maskor.fh-aachen.de/en/) (MASKOR) at [FH Aachen](https://www.fh-aachen.de/en/) and the [Boston Dynamics AI Institute](https://theaiinstitute.com/).
+This project is a collaboration between the [Mobile Autonomous Systems & Cognitive Robotics Institute](https://maskor.fh-aachen.de/en/) (MASKOR) at [FH Aachen](https://www.fh-aachen.de/en/) and [The AI Institute](https://theaiinstitute.com/).
MASKOR contributors:
From 34af0fac857b7c58d7c2969e12e34a3dbf12bb03 Mon Sep 17 00:00:00 2001
From: Katie Hughes
Date: Fri, 20 Dec 2024 11:48:21 -0500
Subject: [PATCH 04/23] New logo
Signed-off-by: Katie Hughes
---
README.md | 21 +++++++++++----------
spot.png | Bin 443005 -> 287882 bytes
spot_driver/README.md | 3 +++
3 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 9b859fac6..bc3f66990 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ git submodule init
git submodule update
```
-Next, run the following script to install the necessary Boston Dynamics and ROS dependencies.
+Next, run the following script to install the necessary Boston Dynamics packages (both Python and C++) and ROS dependencies.
The install script takes the optional argument ```--arm64```; it otherwise defaults to an AMD64 install.
```bash
./install_spot_ros2.sh
@@ -76,7 +76,8 @@ The following flags may be useful for extra functionality when running the image
# Packages
-This repo consists of a series of ROS 2 packages for usage with Spot. Further documentation on how each of these packages can be used can be found in their resepective README's.
+This repository consists of a series of ROS 2 packages for usage with Spot.
+Further documentation on how each of these packages can be used can be found in their resepective README's.
* [`spot_description`](spot_description): contains the URDF of Spot and some simple launchfiles for visualization.
* [`spot_driver`](spot_driver): Core driver for operating Spot. This contains all of the necessary topics, services, and actions for controlling Spot and receiving state information over ROS 2.
@@ -88,7 +89,7 @@ The following packages are used to enable joint level control of Spot via ROS 2
* [`spot_hardware_interface`](spot_hardware_interface): Creates a ROS 2 control hardware interface plugin for operating Spot with the joint level API.
* [`spot_controllers`](spot_controllers): Holds some simple forwarding controller plugins useful for sending commands.
-This package also pulls in some relevant packages as submodules, listed below.
+This package also pulls in the following packages as submodules:
* [`ros_utilities`](https://github.com/bdaiinstitute/ros_utilities): The AI Institute's convenience wrappers around ROS 2.
* [`spot_wrapper`](https://github.com/bdaiinstitute/spot_wrapper): A Python wrapper around the Spot SDK, shared as a common entry point with Spot's ROS 1 repo.
@@ -100,7 +101,7 @@ It can be installed from source as a normal ROS package [here](https://github.co
# Help
-If you encounter problems when using this repository, feel free to open an [issue](https://github.com/bdaiinstitute/spot_ros2/issues) or ask a question in the [discussions](https://github.com/bdaiinstitute/spot_ros2/discussions).
+If you encounter problems when using this repository, feel free to ask a question in the [discussions](https://github.com/bdaiinstitute/spot_ros2/discussions), or open an [issue](https://github.com/bdaiinstitute/spot_ros2/issues) describing the problem in context.
## Verify Package Versions
If you encounter `ModuleNotFoundErrors` with `bosdyn` packages upon running the driver, it is likely that the necessary Boston Dynamics API packages did not get installed with `install_spot_ros2.sh`. To check this, you can run the following command. Note that all versions should be `4.1.0`.
@@ -145,17 +146,17 @@ MIT license - parts of the code developed specifically for ROS 2.
BSD3 license - parts of the code derived from the Clearpath Robotics ROS 1 driver.
# Contributing
-To contribute, install `pre-commit` via pip, run `pre-commit install` and then run `pre-commit run --all-files` to
-verify that your code will pass inspection.
+Code contributions are welcome in this repository!
+
+* Fork this repository, and follow the installation steps
+* Install the pre-commit hooks:
```bash
cd /src/spot_ros2
-pip3 install pre-commit
+pip install pre-commit
pre-commit install
pre-commit run --all-files
```
-
-Now whenever you commit code to this repository, it will be checked against our `pre-commit` hooks.
-You can also run `git commit --no-verify` if you wish to commit without checking against the hooks.
+* Make the intended changes, and open a pull request against this repository. You will need to fill out the [pull request template](pull_request_template.md) to specify what change is being made and why, and how it was tested.
## Contributors
diff --git a/spot.png b/spot.png
index 50e089c6b53281f1eac38c60f6129bdd7304476c..a784e2b4e501a1a047b6890b5ccbd68af073bbdb 100644
GIT binary patch
literal 287882
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