diff --git a/ros2/.devcontainer/Dockerfile b/ros2/.devcontainer/Dockerfile index 6f4db17a..301c4e1f 100644 --- a/ros2/.devcontainer/Dockerfile +++ b/ros2/.devcontainer/Dockerfile @@ -1,26 +1,5 @@ -# TODO: Update the base image to ros:humble-ros-base as soon as updated GPG keys are available -FROM ubuntu:22.04 +FROM ros:humble-ros-base -ENV DEBIAN_FRONTEND=noninteractive -ENV LANG=en_US.UTF-8 -ENV LC_ALL=en_US.UTF-8 - -# Install ros:humble-ros-base -RUN apt-get update && apt-get install -y \ - locales software-properties-common curl && \ - locale-gen en_US en_US.UTF-8 && \ - update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 && \ - add-apt-repository universe && \ - export ROS_APT_SOURCE_VERSION=$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F "tag_name" | awk -F\" '{print $4}') && \ - curl -L -o /tmp/ros2-apt-source.deb "https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo $VERSION_CODENAME)_all.deb" && \ - apt-get install -y /tmp/ros2-apt-source.deb && \ - apt-get update && apt-get install -y --fix-missing ros-humble-desktop && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - echo "source /opt/ros/humble/setup.bash" >> /etc/bash.bashrc - -# END of TODO - -# Install dependencies RUN apt-get update && apt-get install -y \ # Install libfranka build depencencies build-essential \ @@ -29,45 +8,13 @@ RUN apt-get update && apt-get install -y \ libeigen3-dev \ libfmt-dev \ libpoco-dev \ - # Install ros packages - libignition-gazebo6-dev \ - libignition-plugin-dev \ - ros-humble-ament-cmake-clang-format \ - ros-humble-ament-cmake-clang-tidy \ - ros-humble-backward-ros \ - ros-humble-control-msgs \ - ros-humble-controller-interface \ - ros-humble-controller-manager \ - ros-humble-generate-parameter-library \ - ros-humble-hardware-interface \ - ros-humble-hardware-interface-testing \ - ros-humble-joint-state-broadcaster \ - ros-humble-joint-state-publisher \ - ros-humble-joint-state-publisher-gui \ - ros-humble-joint-trajectory-controller \ - ros-humble-moveit-kinematics \ - ros-humble-moveit-planners-ompl \ - ros-humble-moveit-ros-move-group \ - ros-humble-moveit-ros-visualization \ - ros-humble-moveit-simple-controller-manager \ ros-humble-pinocchio \ - ros-humble-realtime-tools \ - ros-humble-ros2-control-test-assets \ - ros-humble-ros2-controllers \ - ros-humble-ros2controlcli \ - ros-humble-rviz2 \ - ros-humble-sdformat-urdf \ - ros-humble-xacro \ - # Install python3 packages - python3-colcon-common-extensions\ + # Install ros packages + python3-colcon-common-extensions \ python3-colcon-mixin \ python3-pip \ - && rm -rf /var/lib/apt/lists/* \ - && pip3 install -U dynamixel_sdk \ - tyro \ - vcstool \ - black - + python3-vcstool \ + && rm -rf /var/lib/apt/lists/* # Install libfranka ARG LIBFRANKA_VERSION=0.15.0 @@ -85,17 +32,20 @@ RUN /bin/bash -c "source /opt/ros/humble/setup.bash && \ # Install franka_ros2 ARG FRANKA_ROS2_VERSION=v1.0.0 ARG FRANKA_DESCRIPTION_VERSION=0.4.0 -RUN /bin/bash -c 'source /opt/ros/humble/setup.bash && \ +RUN /bin/bash -c 'source /opt/ros/humble/setup.bash && \ mkdir -p /tmp/franka_ros2 && cd /tmp/franka_ros2 && \ git clone --recursive https://github.com/frankaemika/franka_ros2.git --branch ${FRANKA_ROS2_VERSION} && \ - git clone --recursive https://github.com/frankaemika/franka_description.git --branch ${FRANKA_DESCRIPTION_VERSION} &&\ + git clone --recursive https://github.com/frankaemika/franka_description.git --branch ${FRANKA_DESCRIPTION_VERSION} && \ + apt-get update && \ + rosdep update && \ + rosdep install --from-paths . --ignore-src -r -y && \ + rm -rf /var/lib/apt/lists/* && \ colcon build --install-base /opt/ros/humble/franka --cmake-args -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off && \ - cd .. &&\ + cd .. && \ rm -rf /tmp/franka_ros2 && \ - echo "source /opt/ros/humble/franka/setup.bash" >> ~/.bashrc &&\ + echo "source /opt/ros/humble/franka/setup.bash" >> ~/.bashrc && \ echo "source /opt/ros/humble/franka/setup.sh" >> ~/.profile' - # Install robotiq gripper ARG ROS2_ROBOTIQ_GRIPPER_COMMIT_HASH="2ff85455d4b9f973c4b0bab1ce95fb09367f0d26" RUN /bin/bash -c "source /opt/ros/humble/setup.bash && \ @@ -105,6 +55,9 @@ RUN /bin/bash -c "source /opt/ros/humble/setup.bash && \ cd ros2_robotiq_gripper && git checkout ${ROS2_ROBOTIQ_GRIPPER_COMMIT_HASH} && cd .. && \ sed -i 's/kGripperMaxSpeed = 0.150;/kGripperMaxSpeed = 1.0;/g' ros2_robotiq_gripper/robotiq_driver/src/hardware_interface.cpp && \ vcs import . --input ros2_robotiq_gripper/ros2_robotiq_gripper.humble.repos && \ + apt-get update && \ + rosdep update && \ + rosdep install --from-paths . --ignore-src -r -y && \ colcon build --install-base /opt/ros/humble/franka --cmake-args -DCMAKE_BUILD_TYPE=Release && \ cd .. && \ rm -rf tmp_build && \ diff --git a/ros2/.devcontainer/devcontainer.json b/ros2/.devcontainer/devcontainer.json index 0f64954a..c2cfee66 100644 --- a/ros2/.devcontainer/devcontainer.json +++ b/ros2/.devcontainer/devcontainer.json @@ -37,5 +37,5 @@ }, "workspaceFolder": "/workspace/", "shutdownAction": "stopCompose", - "postCreateCommand": "echo \"source /workspace/install/setup.bash \">> ~/.bashrc" + "postCreateCommand": "/bin/bash /workspace/ros2/install_workspace_dependencies.bash && echo 'source /workspace/ros2/install/setup.bash' >> ~/.bashrc" } diff --git a/ros2/.devcontainer/docker-compose.yml b/ros2/.devcontainer/docker-compose.yml index b62c934b..c9247bf8 100644 --- a/ros2/.devcontainer/docker-compose.yml +++ b/ros2/.devcontainer/docker-compose.yml @@ -5,11 +5,6 @@ services: restart: no network_mode: host volumes: - - ../:/workspace - - ../../gello/dynamixel/driver.py:/workspace/src/franka_gello_state_publisher/franka_gello_state_publisher/driver.py + - ../../:/workspace - /dev/serial/by-id:/dev/serial/by-id - # GELLO Calibration Files - - ../../scripts/gello_get_offset.py:/workspace/gello_get_offset.py - - ../../gello/dynamixel:/workspace/gello/dynamixel - # Python flake8 config - - ../../.flake8:/workspace/.flake8 \ No newline at end of file + \ No newline at end of file diff --git a/ros2/README.md b/ros2/README.md index 214cbb5b..c84ef5ee 100644 --- a/ros2/README.md +++ b/ros2/README.md @@ -37,9 +37,31 @@ This package provides a ROS 2 node for managing the gripper connected to the Fra ## VS-Code Dev-Container -We recommend working inside the provided VS-Code Dev-Container for a seamless development experience. Dev-Containers allow you to use a consistent environment with all necessary dependencies pre-installed. For more information, refer to the [VS-Code Dev-Containers documentation](https://code.visualstudio.com/docs/devcontainers/containers). +We recommend working inside the provided VS-Code Dev-Container for a seamless development experience. Dev-Containers allow you to use a consistent environment with all necessary dependencies pre-installed. -If you choose not to use the Dev-Container, you will need to manually install the dependencies listed in the `Dockerfile` located in the `.devcontainer` folder. +To start the Dev-Container, open the `ros2` sub-folder of this repository (not the entire `gello_software` folder) in VS Code. If prompted, select **"Reopen in Container"** to launch the workspace inside the Dev-Container. If you are not prompted, open the Command Palette (`Ctrl+Shift+P`) and select **"Dev Containers: Reopen in Container"**. Building the container for the first time will take a few minutes. For more information, refer to the [VS-Code Dev-Containers documentation](https://code.visualstudio.com/docs/devcontainers/containers). + +If you choose not to use the Dev-Container, please refer to the [Local Setup](#local-setup) section below for manual installation instructions. + +## Local Setup + +### Prerequisites + +- **ROS 2 Humble Desktop** must be installed. + See the [official installation guide](https://docs.ros.org/en/humble/Installation.html) for instructions. +- **libfranka** and **franka_ros2** must be installed. + Refer to the [Franka Robotics documentation](https://frankaemika.github.io/docs/index.html) for installation steps and compatibility information. +- **ros2_robotiq_gripper** (if required) must be installed. + See the [ros2_robotiq_gripper GitHub repository](https://github.com/PickNikRobotics/ros2_robotiq_gripper) for installation and usage instructions. + +> 💡 **Hint:** +> You can also find example installation commands for `libfranka`, `franka_ros2`, and `ros2_robotiq_gripper` in the [Dockerfile](./.devcontainer/Dockerfile) located in the `ros2/.devcontainer` directory. These commands can be copy-pasted for your local setup. + +### Further Dependency Installations + +After installing the prerequisites, you may need to install additional dependencies required by this workspace. For this you can run the `install_workspace_dependencies.bash` script. + +If you add new dependencies to your packages, remember to update the relevant `requirements.txt`, `requirements_dev.txt` or `package.xml` files and re-run the script. ## Build and Test @@ -59,6 +81,9 @@ The packages come with a set of tests, which can be executed using the following colcon test ``` +> ⚠️ **Important:** +> All commands for building and testing must be executed from the `ros2` directory of this repository. + ## Getting Started ### 1. **Run the GELLO Publisher** @@ -83,7 +108,7 @@ In this case, the `GELLO_USB_ID` would be `/dev/serial/by-id/usb-FTDI_USB__-__Se If not done already, follow the instructions of the `Create the GELLO configuration and determining joint ID's` section in the main README.md. Use the provided script to configure the GELLO for Franka FR3: ```bash -python3 gello_get_offset.py \ +python3 scripts/gello_get_offset.py \ --start-joints 0 0 0 -1.57 0 1.57 0 \ --joint-signs 1 1 1 1 1 -1 1 \ --port /dev/serial/by-id/ @@ -127,7 +152,7 @@ ros2 launch franka_gello_state_publisher main.launch.py com_port:=/dev/serial/by - **For the Robotiq 2F-85**: ```bash - ros2 launch franka_gripper_manager robotiq_gripper_controller_client.launch.py com_port:= + ros2 launch franka_gripper_manager robotiq_gripper_controller_client.launch.py com_port:=/dev/serial/by-id/ ``` The `ROBOTIQ_USB_ID` can be determined by `ls /dev/serial/by-id`. diff --git a/ros2/install_workspace_dependencies.bash b/ros2/install_workspace_dependencies.bash new file mode 100644 index 00000000..191877c7 --- /dev/null +++ b/ros2/install_workspace_dependencies.bash @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +pip3 install -r ros2/requirements.txt +pip3 install -r ros2/requirements_dev.txt + +apt-get update +rosdep update +rosdep install --from-paths ros2 --ignore-src -r -y \ No newline at end of file diff --git a/ros2/requirements.txt b/ros2/requirements.txt new file mode 100644 index 00000000..16b0ef50 --- /dev/null +++ b/ros2/requirements.txt @@ -0,0 +1,2 @@ +dynamixel_sdk +tyro \ No newline at end of file diff --git a/ros2/requirements_dev.txt b/ros2/requirements_dev.txt new file mode 100644 index 00000000..b3ca9099 --- /dev/null +++ b/ros2/requirements_dev.txt @@ -0,0 +1 @@ +black \ No newline at end of file diff --git a/ros2/src/franka_fr3_arm_controllers/CMakeLists.txt b/ros2/src/franka_fr3_arm_controllers/CMakeLists.txt index b58a4309..bca0b73d 100644 --- a/ros2/src/franka_fr3_arm_controllers/CMakeLists.txt +++ b/ros2/src/franka_fr3_arm_controllers/CMakeLists.txt @@ -109,7 +109,7 @@ if(BUILD_TESTING) ament_cppcheck(${CPP_DIRECTORIES}) ament_lint_cmake(CMakeLists.txt) ament_xmllint() - ament_clang_format(CONFIG_FILE /workspace/.clang-format ${CPP_DIRECTORIES}) + ament_clang_format(CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format ${CPP_DIRECTORIES}) ament_copyright(${CPP_DIRECTORIES} package.xml) ament_add_gtest( @@ -125,7 +125,7 @@ endif() if(CHECK_TIDY) find_package(ament_cmake_clang_tidy REQUIRED) - set(ament_cmake_clang_tidy_CONFIG_FILE /workspace/.clang-tidy) + set(ament_cmake_clang_tidy_CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy) ament_clang_tidy(${CMAKE_BINARY_DIR}) endif() diff --git a/ros2/src/franka_fr3_arm_controllers/package.xml b/ros2/src/franka_fr3_arm_controllers/package.xml index 960d30ec..ad761710 100644 --- a/ros2/src/franka_fr3_arm_controllers/package.xml +++ b/ros2/src/franka_fr3_arm_controllers/package.xml @@ -13,9 +13,10 @@ controller_interface pluginlib rclcpp_lifecycle + hardware_interface franka_msgs - franka_semantic_components - + moveit_core + moveit_msgs ament_cmake_clang_format ament_cmake_copyright diff --git a/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/driver.py b/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/driver.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/gello_publisher.py b/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/gello_publisher.py index 6c7d9f3f..3154a39b 100644 --- a/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/gello_publisher.py +++ b/ros2/src/franka_gello_state_publisher/franka_gello_state_publisher/gello_publisher.py @@ -1,18 +1,14 @@ import os +import sys import glob from typing import Tuple import rclpy from rclpy.node import Node import numpy as np - -try: - from franka_gello_state_publisher.driver import DynamixelDriver -except ImportError: - from driver import DynamixelDriver from sensor_msgs.msg import JointState from std_msgs.msg import Float32 import yaml -from ament_index_python.packages import get_package_share_directory +from ament_index_python.packages import get_package_share_directory, get_package_prefix class GelloPublisher(Node): @@ -72,6 +68,9 @@ def get_values_from_config(self, config_file: str): """Whether or not the gripper is attached.""" joint_ids = list(range(1, self.num_joints + 1)) + self.add_dynamixel_driver_path() + from gello.dynamixel.driver import DynamixelDriver + self.driver = DynamixelDriver(joint_ids, port=self.com_port, baudrate=57600) """The driver for the Dynamixel motors.""" @@ -119,6 +118,12 @@ def gripper_readout_to_percent(self, gripper_position: float) -> float: ) return max(0.0, min(1.0, gripper_percent)) + def add_dynamixel_driver_path(self): + gello_path = os.path.abspath( + os.path.join(get_package_prefix("franka_gello_state_publisher"), "../../../") + ) + sys.path.insert(0, gello_path) + def main(args=None): rclpy.init(args=args) diff --git a/ros2/src/franka_gello_state_publisher/test/test_flake8.py b/ros2/src/franka_gello_state_publisher/test/test_flake8.py index 75c3f199..a7decfff 100644 --- a/ros2/src/franka_gello_state_publisher/test/test_flake8.py +++ b/ros2/src/franka_gello_state_publisher/test/test_flake8.py @@ -20,12 +20,8 @@ @pytest.mark.flake8 @pytest.mark.linter def test_flake8(): - config_path = Path(__file__).resolve().parents[3] / ".flake8" - excluded_file = Path(__file__).resolve().parents[1] / "franka_gello_state_publisher/driver.py" - print(excluded_file) - rc, errors = main_with_errors( - argv=["--config", str(config_path), "--exclude", str(excluded_file)] - ) + config_path = Path(__file__).resolve().parents[4] / ".flake8" + rc, errors = main_with_errors(argv=["--config", str(config_path)]) assert rc == 0, "Found %d code style errors / warnings:\n" % len(errors) + "\n".join(errors) diff --git a/ros2/src/franka_gripper_manager/package.xml b/ros2/src/franka_gripper_manager/package.xml index 9db7ed1e..5c31c247 100644 --- a/ros2/src/franka_gripper_manager/package.xml +++ b/ros2/src/franka_gripper_manager/package.xml @@ -6,8 +6,7 @@ Provisioning of gripper clients for gello Franka Robotics GmbH Apache 2.0 - ros2-robotiq-gripper - sensor-msgs + sensor_msgs rclpy ament_copyright diff --git a/ros2/src/franka_gripper_manager/test/test_flake8.py b/ros2/src/franka_gripper_manager/test/test_flake8.py index 04ef6981..8a2a2afb 100644 --- a/ros2/src/franka_gripper_manager/test/test_flake8.py +++ b/ros2/src/franka_gripper_manager/test/test_flake8.py @@ -20,6 +20,6 @@ @pytest.mark.flake8 @pytest.mark.linter def test_flake8(): - config_path = Path(__file__).resolve().parents[3] / ".flake8" + config_path = Path(__file__).resolve().parents[4] / ".flake8" rc, errors = main_with_errors(argv=["--config", str(config_path)]) assert rc == 0, "Found %d code style errors / warnings:\n" % len(errors) + "\n".join(errors) diff --git a/scripts/gello_get_offset.py b/scripts/gello_get_offset.py index 4e5aa8ca..152ffb91 100644 --- a/scripts/gello_get_offset.py +++ b/scripts/gello_get_offset.py @@ -4,6 +4,10 @@ import numpy as np import tyro +import sys +import os + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from gello.dynamixel.driver import DynamixelDriver