From d44c148c9bad1b5d42496daf2190f26fc9bbfc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 22 Feb 2023 15:56:49 +0100 Subject: [PATCH 1/3] git move files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero --- {ign_ros2_control => gz_ros2_control}/CHANGELOG.rst | 0 {ign_ros2_control => gz_ros2_control}/CMakeLists.txt | 0 {ign_ros2_control => gz_ros2_control}/LICENSE | 0 .../gz_hardware_plugins.xml | 0 .../include/gz_ros2_control/gz_ros2_control_plugin.hpp | 0 .../include/gz_ros2_control/gz_system.hpp | 0 .../include/gz_ros2_control/gz_system_interface.hpp | 0 {ign_ros2_control => gz_ros2_control}/package.xml | 0 .../src/gz_ros2_control_plugin.cpp | 0 .../src/ign_system.cpp => gz_ros2_control/src/gz_system.cpp | 0 {ign_ros2_control_demos => gz_ros2_control_demos}/CHANGELOG.rst | 0 {ign_ros2_control_demos => gz_ros2_control_demos}/CMakeLists.txt | 0 .../config/cartpole_controller_effort.yaml | 0 .../config/cartpole_controller_position.yaml | 0 .../config/cartpole_controller_velocity.yaml | 0 .../config/diff_drive_controller_velocity.yaml | 0 .../config/gripper_controller.yaml | 0 .../config/tricycle_drive_controller.yaml | 0 .../examples/example_diff_drive.cpp | 0 .../examples/example_effort.cpp | 0 .../examples/example_gripper.cpp | 0 .../examples/example_position.cpp | 0 .../examples/example_tricycle_drive.cpp | 0 .../examples/example_velocity.cpp | 0 .../launch/cart_example_effort.launch.py | 0 .../launch/cart_example_position.launch.py | 0 .../launch/cart_example_velocity.launch.py | 0 .../launch/diff_drive_example.launch.py | 0 .../launch/gripper_mimic_joint_example.launch.py | 0 .../launch/tricycle_drive_example.launch.py | 0 {ign_ros2_control_demos => gz_ros2_control_demos}/package.xml | 0 .../urdf/test_cart_effort.xacro.urdf | 0 .../urdf/test_cart_position.xacro.urdf | 0 .../urdf/test_cart_velocity.xacro.urdf | 0 .../urdf/test_diff_drive.xacro.urdf | 0 .../urdf/test_gripper_mimic_joint.xacro.urdf | 0 .../urdf/test_tricycle_drive.xacro.urdf | 0 37 files changed, 0 insertions(+), 0 deletions(-) rename {ign_ros2_control => gz_ros2_control}/CHANGELOG.rst (100%) rename {ign_ros2_control => gz_ros2_control}/CMakeLists.txt (100%) rename {ign_ros2_control => gz_ros2_control}/LICENSE (100%) rename ign_ros2_control/ign_hardware_plugins.xml => gz_ros2_control/gz_hardware_plugins.xml (100%) rename ign_ros2_control/include/ign_ros2_control/ign_ros2_control_plugin.hpp => gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp (100%) rename ign_ros2_control/include/ign_ros2_control/ign_system.hpp => gz_ros2_control/include/gz_ros2_control/gz_system.hpp (100%) rename ign_ros2_control/include/ign_ros2_control/ign_system_interface.hpp => gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp (100%) rename {ign_ros2_control => gz_ros2_control}/package.xml (100%) rename ign_ros2_control/src/ign_ros2_control_plugin.cpp => gz_ros2_control/src/gz_ros2_control_plugin.cpp (100%) rename ign_ros2_control/src/ign_system.cpp => gz_ros2_control/src/gz_system.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/CHANGELOG.rst (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/CMakeLists.txt (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/cartpole_controller_effort.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/cartpole_controller_position.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/cartpole_controller_velocity.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/diff_drive_controller_velocity.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/gripper_controller.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/config/tricycle_drive_controller.yaml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_diff_drive.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_effort.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_gripper.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_position.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_tricycle_drive.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/examples/example_velocity.cpp (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/cart_example_effort.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/cart_example_position.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/cart_example_velocity.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/diff_drive_example.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/gripper_mimic_joint_example.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/launch/tricycle_drive_example.launch.py (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/package.xml (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_cart_effort.xacro.urdf (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_cart_position.xacro.urdf (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_cart_velocity.xacro.urdf (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_diff_drive.xacro.urdf (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_gripper_mimic_joint.xacro.urdf (100%) rename {ign_ros2_control_demos => gz_ros2_control_demos}/urdf/test_tricycle_drive.xacro.urdf (100%) diff --git a/ign_ros2_control/CHANGELOG.rst b/gz_ros2_control/CHANGELOG.rst similarity index 100% rename from ign_ros2_control/CHANGELOG.rst rename to gz_ros2_control/CHANGELOG.rst diff --git a/ign_ros2_control/CMakeLists.txt b/gz_ros2_control/CMakeLists.txt similarity index 100% rename from ign_ros2_control/CMakeLists.txt rename to gz_ros2_control/CMakeLists.txt diff --git a/ign_ros2_control/LICENSE b/gz_ros2_control/LICENSE similarity index 100% rename from ign_ros2_control/LICENSE rename to gz_ros2_control/LICENSE diff --git a/ign_ros2_control/ign_hardware_plugins.xml b/gz_ros2_control/gz_hardware_plugins.xml similarity index 100% rename from ign_ros2_control/ign_hardware_plugins.xml rename to gz_ros2_control/gz_hardware_plugins.xml diff --git a/ign_ros2_control/include/ign_ros2_control/ign_ros2_control_plugin.hpp b/gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp similarity index 100% rename from ign_ros2_control/include/ign_ros2_control/ign_ros2_control_plugin.hpp rename to gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp diff --git a/ign_ros2_control/include/ign_ros2_control/ign_system.hpp b/gz_ros2_control/include/gz_ros2_control/gz_system.hpp similarity index 100% rename from ign_ros2_control/include/ign_ros2_control/ign_system.hpp rename to gz_ros2_control/include/gz_ros2_control/gz_system.hpp diff --git a/ign_ros2_control/include/ign_ros2_control/ign_system_interface.hpp b/gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp similarity index 100% rename from ign_ros2_control/include/ign_ros2_control/ign_system_interface.hpp rename to gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp diff --git a/ign_ros2_control/package.xml b/gz_ros2_control/package.xml similarity index 100% rename from ign_ros2_control/package.xml rename to gz_ros2_control/package.xml diff --git a/ign_ros2_control/src/ign_ros2_control_plugin.cpp b/gz_ros2_control/src/gz_ros2_control_plugin.cpp similarity index 100% rename from ign_ros2_control/src/ign_ros2_control_plugin.cpp rename to gz_ros2_control/src/gz_ros2_control_plugin.cpp diff --git a/ign_ros2_control/src/ign_system.cpp b/gz_ros2_control/src/gz_system.cpp similarity index 100% rename from ign_ros2_control/src/ign_system.cpp rename to gz_ros2_control/src/gz_system.cpp diff --git a/ign_ros2_control_demos/CHANGELOG.rst b/gz_ros2_control_demos/CHANGELOG.rst similarity index 100% rename from ign_ros2_control_demos/CHANGELOG.rst rename to gz_ros2_control_demos/CHANGELOG.rst diff --git a/ign_ros2_control_demos/CMakeLists.txt b/gz_ros2_control_demos/CMakeLists.txt similarity index 100% rename from ign_ros2_control_demos/CMakeLists.txt rename to gz_ros2_control_demos/CMakeLists.txt diff --git a/ign_ros2_control_demos/config/cartpole_controller_effort.yaml b/gz_ros2_control_demos/config/cartpole_controller_effort.yaml similarity index 100% rename from ign_ros2_control_demos/config/cartpole_controller_effort.yaml rename to gz_ros2_control_demos/config/cartpole_controller_effort.yaml diff --git a/ign_ros2_control_demos/config/cartpole_controller_position.yaml b/gz_ros2_control_demos/config/cartpole_controller_position.yaml similarity index 100% rename from ign_ros2_control_demos/config/cartpole_controller_position.yaml rename to gz_ros2_control_demos/config/cartpole_controller_position.yaml diff --git a/ign_ros2_control_demos/config/cartpole_controller_velocity.yaml b/gz_ros2_control_demos/config/cartpole_controller_velocity.yaml similarity index 100% rename from ign_ros2_control_demos/config/cartpole_controller_velocity.yaml rename to gz_ros2_control_demos/config/cartpole_controller_velocity.yaml diff --git a/ign_ros2_control_demos/config/diff_drive_controller_velocity.yaml b/gz_ros2_control_demos/config/diff_drive_controller_velocity.yaml similarity index 100% rename from ign_ros2_control_demos/config/diff_drive_controller_velocity.yaml rename to gz_ros2_control_demos/config/diff_drive_controller_velocity.yaml diff --git a/ign_ros2_control_demos/config/gripper_controller.yaml b/gz_ros2_control_demos/config/gripper_controller.yaml similarity index 100% rename from ign_ros2_control_demos/config/gripper_controller.yaml rename to gz_ros2_control_demos/config/gripper_controller.yaml diff --git a/ign_ros2_control_demos/config/tricycle_drive_controller.yaml b/gz_ros2_control_demos/config/tricycle_drive_controller.yaml similarity index 100% rename from ign_ros2_control_demos/config/tricycle_drive_controller.yaml rename to gz_ros2_control_demos/config/tricycle_drive_controller.yaml diff --git a/ign_ros2_control_demos/examples/example_diff_drive.cpp b/gz_ros2_control_demos/examples/example_diff_drive.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_diff_drive.cpp rename to gz_ros2_control_demos/examples/example_diff_drive.cpp diff --git a/ign_ros2_control_demos/examples/example_effort.cpp b/gz_ros2_control_demos/examples/example_effort.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_effort.cpp rename to gz_ros2_control_demos/examples/example_effort.cpp diff --git a/ign_ros2_control_demos/examples/example_gripper.cpp b/gz_ros2_control_demos/examples/example_gripper.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_gripper.cpp rename to gz_ros2_control_demos/examples/example_gripper.cpp diff --git a/ign_ros2_control_demos/examples/example_position.cpp b/gz_ros2_control_demos/examples/example_position.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_position.cpp rename to gz_ros2_control_demos/examples/example_position.cpp diff --git a/ign_ros2_control_demos/examples/example_tricycle_drive.cpp b/gz_ros2_control_demos/examples/example_tricycle_drive.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_tricycle_drive.cpp rename to gz_ros2_control_demos/examples/example_tricycle_drive.cpp diff --git a/ign_ros2_control_demos/examples/example_velocity.cpp b/gz_ros2_control_demos/examples/example_velocity.cpp similarity index 100% rename from ign_ros2_control_demos/examples/example_velocity.cpp rename to gz_ros2_control_demos/examples/example_velocity.cpp diff --git a/ign_ros2_control_demos/launch/cart_example_effort.launch.py b/gz_ros2_control_demos/launch/cart_example_effort.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/cart_example_effort.launch.py rename to gz_ros2_control_demos/launch/cart_example_effort.launch.py diff --git a/ign_ros2_control_demos/launch/cart_example_position.launch.py b/gz_ros2_control_demos/launch/cart_example_position.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/cart_example_position.launch.py rename to gz_ros2_control_demos/launch/cart_example_position.launch.py diff --git a/ign_ros2_control_demos/launch/cart_example_velocity.launch.py b/gz_ros2_control_demos/launch/cart_example_velocity.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/cart_example_velocity.launch.py rename to gz_ros2_control_demos/launch/cart_example_velocity.launch.py diff --git a/ign_ros2_control_demos/launch/diff_drive_example.launch.py b/gz_ros2_control_demos/launch/diff_drive_example.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/diff_drive_example.launch.py rename to gz_ros2_control_demos/launch/diff_drive_example.launch.py diff --git a/ign_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py b/gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py rename to gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py diff --git a/ign_ros2_control_demos/launch/tricycle_drive_example.launch.py b/gz_ros2_control_demos/launch/tricycle_drive_example.launch.py similarity index 100% rename from ign_ros2_control_demos/launch/tricycle_drive_example.launch.py rename to gz_ros2_control_demos/launch/tricycle_drive_example.launch.py diff --git a/ign_ros2_control_demos/package.xml b/gz_ros2_control_demos/package.xml similarity index 100% rename from ign_ros2_control_demos/package.xml rename to gz_ros2_control_demos/package.xml diff --git a/ign_ros2_control_demos/urdf/test_cart_effort.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_cart_effort.xacro.urdf rename to gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf diff --git a/ign_ros2_control_demos/urdf/test_cart_position.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_cart_position.xacro.urdf rename to gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf diff --git a/ign_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf rename to gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf diff --git a/ign_ros2_control_demos/urdf/test_diff_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_diff_drive.xacro.urdf rename to gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf diff --git a/ign_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf b/gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf rename to gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf diff --git a/ign_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf similarity index 100% rename from ign_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf rename to gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf From a6146783c0e35983beea53c248bfec34da8912e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 22 Feb 2023 15:57:45 +0100 Subject: [PATCH 2/3] Include all changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alejandro Hernández Cordero --- .github/workflows/ci.yaml | 58 ++++-- Dockerfile/Dockerfile | 4 +- README.md | 91 +++++---- gz_ros2_control/CMakeLists.txt | 62 +++--- gz_ros2_control/gz_hardware_plugins.xml | 17 +- .../gz_ros2_control_plugin.hpp | 48 +++-- .../include/gz_ros2_control/gz_system.hpp | 24 +-- .../gz_ros2_control/gz_system_interface.hpp | 22 ++- gz_ros2_control/package.xml | 23 ++- .../src/gz_ros2_control_plugin.cpp | 151 +++++++++------ gz_ros2_control/src/gz_system.cpp | 179 ++++++++++-------- gz_ros2_control_demos/CMakeLists.txt | 2 +- .../config/cartpole_controller_position.yaml | 2 +- .../launch/cart_example_effort.launch.py | 24 +-- .../launch/cart_example_position.launch.py | 20 +- .../launch/cart_example_velocity.launch.py | 26 +-- .../launch/diff_drive_example.launch.py | 20 +- .../gripper_mimic_joint_example.launch.py | 20 +- .../launch/tricycle_drive_example.launch.py | 20 +- gz_ros2_control_demos/package.xml | 14 +- .../urdf/test_cart_effort.xacro.urdf | 8 +- .../urdf/test_cart_position.xacro.urdf | 22 ++- .../urdf/test_cart_velocity.xacro.urdf | 17 +- .../urdf/test_diff_drive.xacro.urdf | 8 +- .../urdf/test_gripper_mimic_joint.xacro.urdf | 27 ++- .../urdf/test_tricycle_drive.xacro.urdf | 8 +- img/gz_ros2_control.gif | Bin 0 -> 83704 bytes 27 files changed, 527 insertions(+), 390 deletions(-) create mode 100644 img/gz_ros2_control.gif diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0da85e3f..809313b7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Ignition ros2 control CI +name: Gazebo ros2 control CI on: pull_request: @@ -10,45 +10,77 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [fortress] + include: + - docker-image: "ubuntu:22.04" + gz-version: "fortress" + ros-distro: "humble" + - docker-image: "ubuntu:22.04" + gz-version: "fortress" + ros-distro: "rolling" + - docker-image: "ubuntu:22.04" + gz-version: "garden" + ros-distro: "humble" + - docker-image: "ubuntu:22.04" + gz-version: "garden" + ros-distro: "rolling" env: - IGNITION_VERSION: ${{ matrix.version }} + DOCKER_IMAGE: ${{ matrix.docker-image }} + GZ_VERSION: ${{ matrix.gz-version }} + ROS_DISTRO: ${{ matrix.ros-distro }} container: - image: ubuntu:22.04 + image: ${{ matrix.docker-image }} steps: - uses: actions/checkout@v2 - name: Setup colcon workspace id: configure + shell: bash run: | export DEBIAN_FRONTEND=noninteractive apt update -qq - apt install -qq -y lsb-release wget curl gnupg2 + apt install -qq -y lsb-release wget curl gnupg2 git cd .. mkdir -p /home/ros2_ws/src + if [ "$ROS_DISTRO" == "rolling" ]; then + git clone https://github.com/gazebosim/ros_gz/ + fi + if [ "$ROS_DISTRO" == "humble" ]; then + git clone https://github.com/gazebosim/ros_gz/ -b humble + fi cp -r gz_ros2_control /home/ros2_ws/src/ - sh -c 'echo "deb http://packages.ros.org/ros2-testing/ubuntu `lsb_release -cs` main" > /etc/apt/sources.list.d/ros2-testing.list' - curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add - + curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null + wget https://packages.osrfoundation.org/gazebo.gpg -O /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null + if [ "$GZ_VERSION" == "garden" ]; then + export GZ_DEPS="libgz-sim7-dev libgz-plugin2-dev" + fi + apt-get update && apt-get upgrade -q -y apt-get update && apt-get install -qq -y \ dirmngr \ python3-colcon-ros \ python3-colcon-common-extensions \ python3-rosdep \ - build-essential + build-essential \ + ${GZ_DEPS} + + if [ "$GZ_VERSION" == "garden" ]; then + export ROSDEP_ARGS="--skip-keys ros_gz_sim --skip-keys gz-plugin2 --skip-keys gz-sim7 --skip-keys gz-transport12 --skip-keys gz-math7 --skip-keys gz-msgs9" + fi cd /home/ros2_ws/src/ rosdep init rosdep update - rosdep install --from-paths ./ -i -y --rosdistro rolling --ignore-src + rosdep install --from-paths ./ -i -y --rosdistro ${ROS_DISTRO} --ignore-src ${ROSDEP_ARGS} - name: Build project id: build run: | cd /home/ros2_ws/ - . /opt/ros/rolling/local_setup.sh - colcon build --packages-up-to ign_ros2_control_demos + . /opt/ros/${ROS_DISTRO}/local_setup.sh + colcon build --packages-up-to gz_ros2_control_demos - name: Run tests id: test run: | cd /home/ros2_ws/ - . /opt/ros/rolling/local_setup.sh - colcon test --event-handlers console_direct+ --packages-select ign_ros2_control ign_ros2_control_demos + . /opt/ros/${ROS_DISTRO}/local_setup.sh + colcon test --event-handlers console_direct+ --packages-select gz_ros2_control gz_ros2_control_demos colcon test-result diff --git a/Dockerfile/Dockerfile b/Dockerfile/Dockerfile index 10e165a0..3b63b741 100644 --- a/Dockerfile/Dockerfile +++ b/Dockerfile/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:22.04 ENV DEBIAN_FRONTEND noninteractive -ENV IGNITION_VERSION fortress +ENV GZ_VERSION fortress ENV ROS_DISTRO rolling # Make sure everything is up to date before building from source @@ -40,4 +40,4 @@ RUN cd /home/ros2_ws/ \ COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] -CMD ros2 launch ign_ros2_control_demos cart_example_position.launch.py +CMD ros2 launch gz_ros2_control_demos cart_example_position.launch.py diff --git a/README.md b/README.md index 789afd86..44214f83 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# ign_ros2_control +# gz_ros2_control ROS2 Distro | Build Status | Package build | :---------: | :----: | :----------: | -[![Licence](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![Build Status](http://build.ros2.org/buildStatus/icon?job=Gdev__ign_ros2_control__ubuntu_focal_amd64)](http://build.ros2.org/job/Gdev__ign_ros2_control__ubuntu_focal_amd64) | [![Build Status](http://build.ros2.org/buildStatus/icon?job=Gbin_uF64__ign_ros2_control__ubuntu_focal_amd64__binary)](http://build.ros2.org/job/Gbin_uF64__ign_ros2_control__ubuntu_focal_amd64__binary) | +[![Licence](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![Build Status](http://build.ros2.org/buildStatus/icon?job=Hdev__ign_ros2_control__ubuntu_focal_amd64)](http://build.ros2.org/job/Hdev__ign_ros2_control__ubuntu_focal_amd64) | [![Build Status](http://build.ros2.org/buildStatus/icon?job=Hbin_uF64__ign_ros2_control__ubuntu_focal_amd64__binary)](http://build.ros2.org/job/Hbin_uF64__ign_ros2_control__ubuntu_focal_amd64__binary) | This is a ROS 2 package for integrating the `ros2_control` controller architecture with the [Ignition Gazebo](http://ignitionrobotics.org/) simulator. More information about `ros2_control` can be found here: https://control.ros.org/ -This package provides an Ignition Gazebo system plugin which instantiates a `ros2_control` controller manager and connects it to a Gazebo model. +This package provides a Gazebo-Sim system plugin which instantiates a `ros2_control` controller manager and connects it to a Gazebo model. [![Build Status](https://github.com/ros-controls/gz_ros2_control/actions/workflows/ci.yaml/badge.svg?branch=galactic)](https://github.com/ros-controls/gz_ros2_control/actions/workflows/ci.yaml) @@ -25,12 +25,11 @@ Rolling | Garden (not released) | [ros2](https://github.com/ros-controls/gz_ros2 # Compile from source -If you want compile this from source, you should choose the Ignition version. The default one is `citadel`: +If you want compile this from source, you should choose the Gazebo version. The default one is `garden`: ```bash -export IGNITION_VERSION=citadel -export IGNITION_VERSION=edifice -export IGNITION_VERSION=fortress +export GZ_VERSION=fortress +export GZ_VERSION=garden ``` Then create a workspace, clone the repo and compile it: @@ -48,7 +47,7 @@ colcon build ## Video + Pictures -![](img/ign_ros2_control.gif) +![](img/gz_ros2_control.gif) ![](img/diff_drive.gif) @@ -59,7 +58,7 @@ colcon build ```bash cd Dockerfile -docker build -t ign_ros2_control . +docker build -t gz_ros2_control . ``` ### To run the demo @@ -69,7 +68,7 @@ docker build -t ign_ros2_control . Docker allows us to run the demo without the GUI if configured properly. The following command runs the demo without the GUI: ```bash -docker run -it --rm --name ignition_ros2_control_demo --net host ign_ros2_control ros2 launch ign_ros2_control_demos cart_example_position.launch.py gui:=false +docker run -it --rm --name gz_ros2_control_demo --net host gz_ros2_control ros2 launch gz_ros2_control_demos cart_example_position.launch.py gui:=false ``` Then on your local machine, you can run the Gazebo client: @@ -84,18 +83,18 @@ To run the demo with the GUI, we are going to use [rocker](https://github.com/os images with customized local support injected for things like nvidia support. Rocker also supports user id specific files for cleaner mounting file permissions. You can install this tool with the following [instructions](https://github.com/osrf/rocker/#installation) (make sure you meet all of the [prerequisites](https://github.com/osrf/rocker/#prerequisites)). -The following command will launch Ignition: +The following command will launch Gazebo: ```bash -rocker --x11 --nvidia --name ignition_ros2_control_demo ign_ros2_control:latest +rocker --x11 --nvidia --name gz_ros2_control_demo gz_ros2_control:latest ``` The following commands allow the cart to be moved along the rail: ```bash -docker exec -it ignition_ros2_control_demo bash +docker exec -it gz_ros2_control_demo bash source /home/ros2_ws/install/setup.bash -ros2 run ign_ros2_control_demos example_position +ros2 run gz_ros2_control_demos example_position ``` ## Add ros2_control tag to a URDF @@ -108,9 +107,9 @@ include: - `` tag including the robot controllers: commands and states. ```xml - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -129,7 +128,7 @@ include: ### Using mimic joints in simulation -To use `mimic` joints in `ign_ros2_control` you should define its parameters to your URDF. +To use `mimic` joints in `gz_ros2_control` you should define its parameters to your URDF. We should include: - `` tag to the mimicked joint ([detailed manual(https://wiki.ros.org/urdf/XML/joint)) @@ -158,31 +157,31 @@ We should include: ``` -## Add the ign_ros2_control plugin +## Add the gz_ros2_control plugin In addition to the `ros2_control` tags, a Gazebo plugin needs to be added to your URDF that actually parses the `ros2_control` tags and loads the appropriate hardware interfaces and -controller manager. By default the `ign_ros2_control` plugin is very simple, though it is also +controller manager. By default the `gz_ros2_control` plugin is very simple, though it is also extensible via an additional plugin architecture to allow power users to create their own custom robot hardware interfaces between `ros2_control` and Gazebo. ```xml - + robot_description robot_state_publisher - $(find ign_ros2_control_demos)/config/cartpole_controller.yaml + $(find gz_ros2_control_demos)/config/cartpole_controller.yaml ``` -The `ign_ros2_control` `` tag also has the following optional child elements: +The `gz_ros2_control` `` tag also has the following optional child elements: - ``: YAML file with the configuration of the controllers -#### Default ign_ros2_control Behavior +#### Default gz_ros2_control Behavior -By default, without a `` tag, `ign_ros2_control` will attempt to get all of the information it needs to interface with a ros2_control-based controller out of the URDF. This is sufficient for most cases, and good for at least getting started. +By default, without a `` tag, `gz_ros2_control` will attempt to get all of the information it needs to interface with a ros2_control-based controller out of the URDF. This is sufficient for most cases, and good for at least getting started. The default behavior provides the following ros2_control interfaces: @@ -190,24 +189,24 @@ The default behavior provides the following ros2_control interfaces: - hardware_interface::EffortJointInterface - hardware_interface::VelocityJointInterface -#### Advanced: custom ign_ros2_control Simulation Plugins +#### Advanced: custom gz_ros2_control Simulation Plugins -The `ign_ros2_control` Gazebo plugin also provides a pluginlib-based interface to implement custom interfaces between Gazebo and `ros2_control` for simulating more complex mechanisms (nonlinear springs, linkages, etc). +The `gz_ros2_control` Gazebo plugin also provides a pluginlib-based interface to implement custom interfaces between Gazebo and `ros2_control` for simulating more complex mechanisms (nonlinear springs, linkages, etc). -These plugins must inherit the `ign_ros2_control::IgnitionSystemInterface`, which implements a simulated `ros2_control` +These plugins must inherit the `gz_ros2_control::GazeboSimSystemInterface`, which implements a simulated `ros2_control` `hardware_interface::SystemInterface`. SystemInterface provides API-level access to read and command joint properties. -The respective IgnitionSystemInterface sub-class is specified in a URDF model and is loaded when the +The respective GazeboSimSystemInterface sub-class is specified in a URDF model and is loaded when the robot model is loaded. For example, the following XML will load the default plugin: ```xml - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem ... - + ... @@ -220,8 +219,8 @@ and use the tag `` to set the controller ma ```xml - - $(find ign_ros2_control_demos)/config/cartpole_controller.yaml + + $(find gz_ros2_control_demos)/config/cartpole_controller.yaml controller_manager @@ -250,16 +249,16 @@ cart_pole_controller: ``` #### Executing the examples -There are some examples in the `ign_ros2_control_demos` package. These examples allow to launch a cart in a 30 meter rail. +There are some examples in the `gz_ros2_control_demos` package. These examples allow to launch a cart in a 30 meter rail. You can run some of the example configurations by running the following commands: ```bash -ros2 launch ign_ros2_control_demos cart_example_position.launch.py -ros2 launch ign_ros2_control_demos cart_example_velocity.launch.py -ros2 launch ign_ros2_control_demos cart_example_effort.launch.py -ros2 launch ign_ros2_control_demos diff_drive_example.launch.py -ros2 launch ign_ros2_control_demos tricycle_drive_example.launch.py +ros2 launch gz_ros2_control_demos cart_example_position.launch.py +ros2 launch gz_ros2_control_demos cart_example_velocity.launch.py +ros2 launch gz_ros2_control_demos cart_example_effort.launch.py +ros2 launch gz_ros2_control_demos diff_drive_example.launch.py +ros2 launch gz_ros2_control_demos tricycle_drive_example.launch.py ``` Send example commands: @@ -267,11 +266,11 @@ Send example commands: When the Gazebo world is launched, you can run some of the following commands to move the cart. ```bash -ros2 run ign_ros2_control_demos example_position -ros2 run ign_ros2_control_demos example_velocity -ros2 run ign_ros2_control_demos example_effort -ros2 run ign_ros2_control_demos example_diff_drive -ros2 run ign_ros2_control_demos example_tricycle_drive +ros2 run gz_ros2_control_demos example_position +ros2 run gz_ros2_control_demos example_velocity +ros2 run gz_ros2_control_demos example_effort +ros2 run gz_ros2_control_demos example_diff_drive +ros2 run gz_ros2_control_demos example_tricycle_drive ``` The following example shows parallel gripper with mimic joint: @@ -280,11 +279,11 @@ The following example shows parallel gripper with mimic joint: ```bash -ros2 launch ign_ros2_control_demos gripper_mimic_joint_example.launch.py +ros2 launch gz_ros2_control_demos gripper_mimic_joint_example.launch.py ``` Send example commands: ```bash -ros2 run ign_ros2_control_demos example_gripper +ros2 run gz_ros2_control_demos example_gripper ``` diff --git a/gz_ros2_control/CMakeLists.txt b/gz_ros2_control/CMakeLists.txt index f11c290f..5439c686 100644 --- a/gz_ros2_control/CMakeLists.txt +++ b/gz_ros2_control/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.5) -project(ign_ros2_control) +project(gz_ros2_control) # Default to C11 if(NOT CMAKE_C_STANDARD) @@ -24,39 +24,41 @@ find_package(pluginlib REQUIRED) find_package(rclcpp REQUIRED) find_package(yaml_cpp_vendor REQUIRED) -if("$ENV{IGNITION_VERSION}" STREQUAL "citadel") - find_package(ignition-gazebo3 REQUIRED) - set(IGN_GAZEBO_VER ${ignition-gazebo3_VERSION_MAJOR}) - message(STATUS "Compiling against Ignition Citadel") +set(GZ_PLUGIN) +set(GZ_SIM) -elseif("$ENV{IGNITION_VERSION}" STREQUAL "edifice") - find_package(ignition-gazebo5 REQUIRED) - set(IGN_GAZEBO_VER ${ignition-gazebo5_VERSION_MAJOR}) - message(STATUS "Compiling against Ignition Edifice") - -elseif("$ENV{IGNITION_VERSION}" STREQUAL "fortress") +if("$ENV{GZ_VERSION}" STREQUAL "fortress") find_package(ignition-gazebo6 REQUIRED) - set(IGN_GAZEBO_VER ${ignition-gazebo6_VERSION_MAJOR}) - message(STATUS "Compiling against Ignition Fortress") - + set(GZ_SIM_VER ${ignition-gazebo6_VERSION_MAJOR}) + message(STATUS "Compiling against Gazebo Fortress") + find_package(ignition-plugin1 REQUIRED) + set(GZ_PLUGIN_VER ${ignition-plugin1_VERSION_MAJOR}) + set(GZ_PLUGIN ignition-plugin${GZ_PLUGIN_VER}::register) + set(GZ_SIM ignition-gazebo${GZ_SIM_VER}::core) else() - find_package(ignition-gazebo6 REQUIRED) - set(IGN_GAZEBO_VER ${ignition-gazebo6_VERSION_MAJOR}) - message(STATUS "Compiling against Ignition Fortress") + find_package(gz-sim7 REQUIRED) + set(GZ_SIM_VER ${gz-sim7_VERSION_MAJOR}) + message(STATUS "Compiling against Gazebo Garden") + find_package(gz-plugin2 REQUIRED) + set(GZ_PLUGIN_VER ${gz-plugin2_VERSION_MAJOR}) + set(GZ_PLUGIN gz-plugin${GZ_PLUGIN_VER}::register) + set(GZ_SIM gz-sim${GZ_SIM_VER}::core) + add_definitions(-DGZ_HEADERS) endif() -find_package(ignition-plugin1 REQUIRED) -set(IGN_PLUGIN_VER ${ignition-plugin1_VERSION_MAJOR}) +if("$ENV{ROS_DISTRO}" STREQUAL "rolling") + add_definitions(-DROLLING) +endif() include_directories(include) add_library(${PROJECT_NAME}-system SHARED - src/ign_ros2_control_plugin.cpp + src/gz_ros2_control_plugin.cpp ) target_link_libraries(${PROJECT_NAME}-system - ignition-gazebo${IGN_GAZEBO_VER}::core - ignition-plugin${IGN_PLUGIN_VER}::register + ${GZ_SIM} + ${GZ_PLUGIN} ) ament_target_dependencies(${PROJECT_NAME}-system ament_index_cpp @@ -70,21 +72,21 @@ ament_target_dependencies(${PROJECT_NAME}-system ######### -add_library(ign_hardware_plugins SHARED - src/ign_system.cpp +add_library(gz_hardware_plugins SHARED + src/gz_system.cpp ) -ament_target_dependencies(ign_hardware_plugins +ament_target_dependencies(gz_hardware_plugins rclcpp_lifecycle hardware_interface rclcpp ) -target_link_libraries(ign_hardware_plugins - ignition-gazebo${IGN_GAZEBO_VER}::core +target_link_libraries(gz_hardware_plugins + ${GZ_SIM} ) ## Install install(TARGETS - ign_hardware_plugins + gz_hardware_plugins ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin @@ -97,14 +99,14 @@ if(BUILD_TESTING) endif() ament_export_include_directories(include) -ament_export_libraries(${PROJECT_NAME} ign_hardware_plugins) +ament_export_libraries(${PROJECT_NAME} gz_hardware_plugins) # Install directories install(TARGETS ${PROJECT_NAME}-system DESTINATION lib ) -pluginlib_export_plugin_description_file(ign_ros2_control ign_hardware_plugins.xml) +pluginlib_export_plugin_description_file(gz_ros2_control gz_hardware_plugins.xml) # Setup the project ament_package() diff --git a/gz_ros2_control/gz_hardware_plugins.xml b/gz_ros2_control/gz_hardware_plugins.xml index 3d245541..40ae92fe 100644 --- a/gz_ros2_control/gz_hardware_plugins.xml +++ b/gz_ros2_control/gz_hardware_plugins.xml @@ -1,10 +1,19 @@ - + + name="gz_ros2_control/GazeboSimSystem" + type="gz_ros2_control::GazeboSimSystem" + base_class_type="gz_ros2_control::GazeboSimSystemInterface"> GazeboPositionJoint + + + GazeboPositionJoint + + + diff --git a/gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp b/gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp index c7be9aef..e7c26603 100644 --- a/gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp +++ b/gz_ros2_control/include/gz_ros2_control/gz_ros2_control_plugin.hpp @@ -12,51 +12,57 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef IGN_ROS2_CONTROL__IGN_ROS2_CONTROL_PLUGIN_HPP_ -#define IGN_ROS2_CONTROL__IGN_ROS2_CONTROL_PLUGIN_HPP_ +#ifndef GZ_ROS2_CONTROL__GZ_ROS2_CONTROL_PLUGIN_HPP_ +#define GZ_ROS2_CONTROL__GZ_ROS2_CONTROL_PLUGIN_HPP_ #include +#ifdef GZ_HEADERS +#include +namespace sim = gz::sim; +#else #include +namespace sim = ignition::gazebo; +#endif -namespace ign_ros2_control +namespace gz_ros2_control { // Forward declarations. -class IgnitionROS2ControlPluginPrivate; +class GazeboSimROS2ControlPluginPrivate; -class IgnitionROS2ControlPlugin - : public ignition::gazebo::System, - public ignition::gazebo::ISystemConfigure, - public ignition::gazebo::ISystemPreUpdate, - public ignition::gazebo::ISystemPostUpdate +class GazeboSimROS2ControlPlugin + : public sim::System, + public sim::ISystemConfigure, + public sim::ISystemPreUpdate, + public sim::ISystemPostUpdate { public: /// \brief Constructor - IgnitionROS2ControlPlugin(); + GazeboSimROS2ControlPlugin(); /// \brief Destructor - ~IgnitionROS2ControlPlugin() override; + ~GazeboSimROS2ControlPlugin() override; // Documentation inherited void Configure( - const ignition::gazebo::Entity & _entity, + const sim::Entity & _entity, const std::shared_ptr & _sdf, - ignition::gazebo::EntityComponentManager & _ecm, - ignition::gazebo::EventManager & _eventMgr) override; + sim::EntityComponentManager & _ecm, + sim::EventManager & _eventMgr) override; // Documentation inherited void PreUpdate( - const ignition::gazebo::UpdateInfo & _info, - ignition::gazebo::EntityComponentManager & _ecm) override; + const sim::UpdateInfo & _info, + sim::EntityComponentManager & _ecm) override; void PostUpdate( - const ignition::gazebo::UpdateInfo & _info, - const ignition::gazebo::EntityComponentManager & _ecm) override; + const sim::UpdateInfo & _info, + const sim::EntityComponentManager & _ecm) override; private: /// \brief Private data pointer. - std::unique_ptr dataPtr; + std::unique_ptr dataPtr; }; -} // namespace ign_ros2_control +} // namespace gz_ros2_control -#endif // IGN_ROS2_CONTROL__IGN_ROS2_CONTROL_PLUGIN_HPP_ +#endif // GZ_ROS2_CONTROL__GZ_ROS2_CONTROL_PLUGIN_HPP_ diff --git a/gz_ros2_control/include/gz_ros2_control/gz_system.hpp b/gz_ros2_control/include/gz_ros2_control/gz_system.hpp index 08dfd847..8ad075a2 100644 --- a/gz_ros2_control/include/gz_ros2_control/gz_system.hpp +++ b/gz_ros2_control/include/gz_ros2_control/gz_system.hpp @@ -13,29 +13,29 @@ // limitations under the License. -#ifndef IGN_ROS2_CONTROL__IGN_SYSTEM_HPP_ -#define IGN_ROS2_CONTROL__IGN_SYSTEM_HPP_ +#ifndef GZ_ROS2_CONTROL__GZ_SYSTEM_HPP_ +#define GZ_ROS2_CONTROL__GZ_SYSTEM_HPP_ #include #include #include #include -#include "ign_ros2_control/ign_system_interface.hpp" +#include "gz_ros2_control/gz_system_interface.hpp" #include "rclcpp_lifecycle/state.hpp" #include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp" -namespace ign_ros2_control +namespace gz_ros2_control { using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn; // Forward declaration -class IgnitionSystemPrivate; +class GazeboSimSystemPrivate; -// These class must inherit `ign_ros2_control::IgnitionSystemInterface` which implements a +// These class must inherit `gz_ros2_control::GazeboSimSystemInterface` which implements a // simulated `ros2_control` `hardware_interface::SystemInterface`. -class IgnitionSystem : public IgnitionSystemInterface +class GazeboSimSystem : public GazeboSimSystemInterface { public: // Documentation Inherited @@ -74,9 +74,9 @@ class IgnitionSystem : public IgnitionSystemInterface // Documentation Inherited bool initSim( rclcpp::Node::SharedPtr & model_nh, - std::map & joints, + std::map & joints, const hardware_interface::HardwareInfo & hardware_info, - ignition::gazebo::EntityComponentManager & _ecm, + sim::EntityComponentManager & _ecm, int & update_rate) override; private: @@ -87,9 +87,9 @@ class IgnitionSystem : public IgnitionSystemInterface const hardware_interface::HardwareInfo & hardware_info); /// \brief Private data class - std::unique_ptr dataPtr; + std::unique_ptr dataPtr; }; -} // namespace ign_ros2_control +} // namespace gz_ros2_control -#endif // IGN_ROS2_CONTROL__IGN_SYSTEM_HPP_ +#endif // GZ_ROS2_CONTROL__GZ_SYSTEM_HPP_ diff --git a/gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp b/gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp index 97e20db8..745864d3 100644 --- a/gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp +++ b/gz_ros2_control/include/gz_ros2_control/gz_system_interface.hpp @@ -13,22 +13,28 @@ // limitations under the License. -#ifndef IGN_ROS2_CONTROL__IGN_SYSTEM_INTERFACE_HPP_ -#define IGN_ROS2_CONTROL__IGN_SYSTEM_INTERFACE_HPP_ +#ifndef GZ_ROS2_CONTROL__GZ_SYSTEM_INTERFACE_HPP_ +#define GZ_ROS2_CONTROL__GZ_SYSTEM_INTERFACE_HPP_ #include #include #include #include +#ifdef GZ_HEADERS +#include +namespace sim = gz::sim; +#else #include +namespace sim = ignition::gazebo; +#endif #include #include #include -namespace ign_ros2_control +namespace gz_ros2_control { /// \brief This class allows us to handle flags easily, instead of using strings @@ -71,7 +77,7 @@ class SafeEnum }; // SystemInterface provides API-level access to read and command joint properties. -class IgnitionSystemInterface +class GazeboSimSystemInterface : public hardware_interface::SystemInterface { public: @@ -84,9 +90,9 @@ class IgnitionSystemInterface /// param[in] update_rate controller update rate virtual bool initSim( rclcpp::Node::SharedPtr & model_nh, - std::map & joints, + std::map & joints, const hardware_interface::HardwareInfo & hardware_info, - ignition::gazebo::EntityComponentManager & _ecm, + sim::EntityComponentManager & _ecm, int & update_rate) = 0; // Methods used to control a joint. @@ -104,6 +110,6 @@ class IgnitionSystemInterface rclcpp::Node::SharedPtr nh_; }; -} // namespace ign_ros2_control +} // namespace gz_ros2_control -#endif // IGN_ROS2_CONTROL__IGN_SYSTEM_INTERFACE_HPP_ +#endif // GZ_ROS2_CONTROL__GZ_SYSTEM_INTERFACE_HPP_ diff --git a/gz_ros2_control/package.xml b/gz_ros2_control/package.xml index 8fba1d44..fb75b803 100644 --- a/gz_ros2_control/package.xml +++ b/gz_ros2_control/package.xml @@ -1,8 +1,8 @@ - ign_ros2_control - 0.6.1 - Ignition ros2_control package allows to control simulated robots using ros2_control framework. + gz_ros2_control + 0.5.0 + Gazebo ros2_control package allows to control simulated robots using ros2_control framework. Alejandro Hernández Alejandro Hernández Apache 2 @@ -10,12 +10,14 @@ ament_cmake ament_index_cpp - - ignition-gazebo6 - ignition-gazebo3 - ignition-gazebo5 - ignition-gazebo6 - ignition-plugin + + gz-sim7 + ignition-gazebo6 + gz-sim7 + + gz-plugin2 + ignition-plugin + gz-plugin2 pluginlib rclcpp yaml_cpp_vendor @@ -28,6 +30,7 @@ ament_cmake - + + diff --git a/gz_ros2_control/src/gz_ros2_control_plugin.cpp b/gz_ros2_control/src/gz_ros2_control_plugin.cpp index 98d02cac..41146e80 100644 --- a/gz_ros2_control/src/gz_ros2_control_plugin.cpp +++ b/gz_ros2_control/src/gz_ros2_control_plugin.cpp @@ -12,20 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include #include #include #include #include +#ifdef GZ_HEADERS +#include +#include +#include +#include +#include +#include +#include +#else #include #include #include #include #include #include - #include +#endif + #include @@ -37,13 +49,13 @@ #include -#include "ign_ros2_control/ign_ros2_control_plugin.hpp" -#include "ign_ros2_control/ign_system.hpp" +#include "gz_ros2_control/gz_ros2_control_plugin.hpp" +#include "gz_ros2_control/gz_system.hpp" -namespace ign_ros2_control +namespace gz_ros2_control { ////////////////////////////////////////////////// -class IgnitionROS2ControlPluginPrivate +class GazeboSimROS2ControlPluginPrivate { public: /// \brief Get the URDF XML from the parameter server @@ -54,14 +66,14 @@ class IgnitionROS2ControlPluginPrivate /// joints are returned /// \param[in] _entity Entity of the model that the plugin is being /// configured for - /// \param[in] _ecm Ignition Entity Component Manager + /// \param[in] _ecm Gazebo Entity Component Manager /// \return List of entities containing all enabled joints - std::map GetEnabledJoints( - const ignition::gazebo::Entity & _entity, - ignition::gazebo::EntityComponentManager & _ecm) const; + std::map GetEnabledJoints( + const sim::Entity & _entity, + sim::EntityComponentManager & _ecm) const; /// \brief Entity ID for sensor within Gazebo. - ignition::gazebo::Entity entity_; + sim::Entity entity_; /// \brief Node Handles std::shared_ptr node_{nullptr}; @@ -80,7 +92,7 @@ class IgnitionROS2ControlPluginPrivate /// \brief Interface loader std::shared_ptr> + gz_ros2_control::GazeboSimSystemInterface>> robot_hw_sim_loader_{nullptr}; /// \brief Controller manager @@ -100,33 +112,33 @@ class IgnitionROS2ControlPluginPrivate rclcpp::Time((int64_t)0, RCL_ROS_TIME); /// \brief ECM pointer - ignition::gazebo::EntityComponentManager * ecm{nullptr}; + sim::EntityComponentManager * ecm{nullptr}; /// \brief controller update rate int update_rate; }; ////////////////////////////////////////////////// -std::map -IgnitionROS2ControlPluginPrivate::GetEnabledJoints( - const ignition::gazebo::Entity & _entity, - ignition::gazebo::EntityComponentManager & _ecm) const +std::map +GazeboSimROS2ControlPluginPrivate::GetEnabledJoints( + const sim::Entity & _entity, + sim::EntityComponentManager & _ecm) const { - std::map output; + std::map output; std::vector enabledJoints; // Get all available joints - auto jointEntities = _ecm.ChildrenByComponents(_entity, ignition::gazebo::components::Joint()); + auto jointEntities = _ecm.ChildrenByComponents(_entity, sim::components::Joint()); // Iterate over all joints and verify whether they can be enabled or not for (const auto & jointEntity : jointEntities) { - const auto jointName = _ecm.Component( + const auto jointName = _ecm.Component( jointEntity)->Data(); // Make sure the joint type is supported, i.e. it has exactly one // actuated axis - const auto * jointType = _ecm.Component(jointEntity); + const auto * jointType = _ecm.Component(jointEntity); switch (jointType->Data()) { case sdf::JointType::PRISMATIC: case sdf::JointType::REVOLUTE: @@ -140,7 +152,7 @@ IgnitionROS2ControlPluginPrivate::GetEnabledJoints( { RCLCPP_INFO( node_->get_logger(), - "[ign_ros2_control] Fixed joint [%s] (Entity=%lu)] is skipped", + "[gz_ros2_control] Fixed joint [%s] (Entity=%lu)] is skipped", jointName.c_str(), jointEntity); continue; } @@ -151,7 +163,7 @@ IgnitionROS2ControlPluginPrivate::GetEnabledJoints( { RCLCPP_WARN( node_->get_logger(), - "[ign_ros2_control] Joint [%s] (Entity=%lu)] is of unsupported type." + "[gz_ros2_control] Joint [%s] (Entity=%lu)] is of unsupported type." " Only joints with a single axis are supported.", jointName.c_str(), jointEntity); continue; @@ -160,7 +172,7 @@ IgnitionROS2ControlPluginPrivate::GetEnabledJoints( { RCLCPP_WARN( node_->get_logger(), - "[ign_ros2_control] Joint [%s] (Entity=%lu)] is of unknown type", + "[gz_ros2_control] Joint [%s] (Entity=%lu)] is of unknown type", jointName.c_str(), jointEntity); continue; } @@ -172,7 +184,7 @@ IgnitionROS2ControlPluginPrivate::GetEnabledJoints( } ////////////////////////////////////////////////// -std::string IgnitionROS2ControlPluginPrivate::getURDF() const +std::string GazeboSimROS2ControlPluginPrivate::getURDF() const { std::string urdf_string; @@ -215,7 +227,7 @@ std::string IgnitionROS2ControlPluginPrivate::getURDF() const break; } else { RCLCPP_ERROR( - node_->get_logger(), "ign_ros2_control plugin is waiting for model" + node_->get_logger(), "gz_ros2_control plugin is waiting for model" " URDF in parameter [%s] on the ROS param server.", this->robot_description_.c_str()); } @@ -227,13 +239,13 @@ std::string IgnitionROS2ControlPluginPrivate::getURDF() const } ////////////////////////////////////////////////// -IgnitionROS2ControlPlugin::IgnitionROS2ControlPlugin() -: dataPtr(std::make_unique()) +GazeboSimROS2ControlPlugin::GazeboSimROS2ControlPlugin() +: dataPtr(std::make_unique()) { } ////////////////////////////////////////////////// -IgnitionROS2ControlPlugin::~IgnitionROS2ControlPlugin() +GazeboSimROS2ControlPlugin::~GazeboSimROS2ControlPlugin() { // Stop controller manager thread this->dataPtr->stop_ = true; @@ -243,19 +255,19 @@ IgnitionROS2ControlPlugin::~IgnitionROS2ControlPlugin() } ////////////////////////////////////////////////// -void IgnitionROS2ControlPlugin::Configure( - const ignition::gazebo::Entity & _entity, +void GazeboSimROS2ControlPlugin::Configure( + const sim::Entity & _entity, const std::shared_ptr & _sdf, - ignition::gazebo::EntityComponentManager & _ecm, - ignition::gazebo::EventManager &) + sim::EntityComponentManager & _ecm, + sim::EventManager &) { // Make sure the controller is attached to a valid model - const auto model = ignition::gazebo::Model(_entity); + const auto model = sim::Model(_entity); if (!model.Valid(_ecm)) { RCLCPP_ERROR( this->dataPtr->node_->get_logger(), - "[Ignition ROS 2 Control] Failed to initialize because [%s] (Entity=%lu)] is not a model." - "Please make sure that Ignition ROS 2 Control is attached to a valid model.", + "[Gazebo ROS 2 Control] Failed to initialize because [%s] (Entity=%lu)] is not a model." + "Please make sure that Gazebo ROS 2 Control is attached to a valid model.", model.Name(_ecm).c_str(), _entity); return; } @@ -266,7 +278,7 @@ void IgnitionROS2ControlPlugin::Configure( if (paramFileName.empty()) { RCLCPP_ERROR( this->dataPtr->node_->get_logger(), - "Ignition ros2 control found an empty parameters file. Failed to initialize."); + "Gazebo ros2 control found an empty parameters file. Failed to initialize."); return; } @@ -327,7 +339,7 @@ void IgnitionROS2ControlPlugin::Configure( if (!rclcpp::ok()) { rclcpp::init(static_cast(argv.size()), argv.data()); - std::string node_name = "ignition_ros_control"; + std::string node_name = "gz_ros_control"; if (!controllerManagerPrefixNodeName.empty()) { node_name = controllerManagerPrefixNodeName + "_" + node_name; } @@ -345,7 +357,7 @@ void IgnitionROS2ControlPlugin::Configure( this->dataPtr->thread_executor_spin_ = std::thread(spin); RCLCPP_DEBUG_STREAM( - this->dataPtr->node_->get_logger(), "[Ignition ROS 2 Control] Setting up controller for [" << + this->dataPtr->node_->get_logger(), "[Gazebo Sim ROS 2 Control] Setting up controller for [" << model.Name(_ecm) << "] (Entity=" << _entity << ")]."); // Get list of enabled joints @@ -356,7 +368,7 @@ void IgnitionROS2ControlPlugin::Configure( if (enabledJoints.size() == 0) { RCLCPP_DEBUG_STREAM( this->dataPtr->node_->get_logger(), - "[Ignition ROS 2 Control] There are no available Joints."); + "[Gazebo ROS 2 Control] There are no available Joints."); return; } @@ -371,7 +383,7 @@ void IgnitionROS2ControlPlugin::Configure( } catch (const std::runtime_error & ex) { RCLCPP_ERROR_STREAM( this->dataPtr->node_->get_logger(), - "Error parsing URDF in ign_ros2_control plugin, plugin not active : " << ex.what()); + "Error parsing URDF in gz_ros2_control plugin, plugin not active : " << ex.what()); return; } @@ -380,9 +392,9 @@ void IgnitionROS2ControlPlugin::Configure( try { this->dataPtr->robot_hw_sim_loader_.reset( - new pluginlib::ClassLoader( - "ign_ros2_control", - "ign_ros2_control::IgnitionSystemInterface")); + new pluginlib::ClassLoader( + "gz_ros2_control", + "gz_ros2_control::GazeboSimSystemInterface")); } catch (pluginlib::LibraryLoadException & ex) { RCLCPP_ERROR( this->dataPtr->node_->get_logger(), "Failed to create robot simulation interface loader: %s ", @@ -391,11 +403,15 @@ void IgnitionROS2ControlPlugin::Configure( } for (unsigned int i = 0; i < control_hardware_info.size(); ++i) { +#ifndef ROLLING + std::string robot_hw_sim_type_str_ = control_hardware_info[i].hardware_class_type; +#else std::string robot_hw_sim_type_str_ = control_hardware_info[i].hardware_plugin_name; - auto ignitionSystem = std::unique_ptr( +#endif + auto gzSimSystem = std::unique_ptr( this->dataPtr->robot_hw_sim_loader_->createUnmanagedInstance(robot_hw_sim_type_str_)); - if (!ignitionSystem->initSim( + if (!gzSimSystem->initSim( this->dataPtr->node_, enabledJoints, control_hardware_info[i], @@ -407,7 +423,7 @@ void IgnitionROS2ControlPlugin::Configure( return; } - resource_manager_->import_component(std::move(ignitionSystem), control_hardware_info[i]); + resource_manager_->import_component(std::move(gzSimSystem), control_hardware_info[i]); rclcpp_lifecycle::State state( lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, @@ -445,9 +461,9 @@ void IgnitionROS2ControlPlugin::Configure( } ////////////////////////////////////////////////// -void IgnitionROS2ControlPlugin::PreUpdate( - const ignition::gazebo::UpdateInfo & _info, - ignition::gazebo::EntityComponentManager & /*_ecm*/) +void GazeboSimROS2ControlPlugin::PreUpdate( + const sim::UpdateInfo & _info, + sim::EntityComponentManager & /*_ecm*/) { static bool warned{false}; if (!warned) { @@ -479,9 +495,9 @@ void IgnitionROS2ControlPlugin::PreUpdate( } ////////////////////////////////////////////////// -void IgnitionROS2ControlPlugin::PostUpdate( - const ignition::gazebo::UpdateInfo & _info, - const ignition::gazebo::EntityComponentManager & /*_ecm*/) +void GazeboSimROS2ControlPlugin::PostUpdate( + const sim::UpdateInfo & _info, + const sim::EntityComponentManager & /*_ecm*/) { // Get the simulation time and period rclcpp::Time sim_time_ros(std::chrono::duration_cast( @@ -490,18 +506,33 @@ void IgnitionROS2ControlPlugin::PostUpdate( if (sim_period >= this->dataPtr->control_period_) { this->dataPtr->last_update_sim_time_ros_ = sim_time_ros; - auto ign_controller_manager = - std::dynamic_pointer_cast( + auto gz_controller_manager = + std::dynamic_pointer_cast( this->dataPtr->controller_manager_); this->dataPtr->controller_manager_->read(sim_time_ros, sim_period); this->dataPtr->controller_manager_->update(sim_time_ros, sim_period); } } -} // namespace ign_ros2_control - +} // namespace gz_ros2_control + +#ifdef GZ_HEADERS +GZ_ADD_PLUGIN( + gz_ros2_control::GazeboSimROS2ControlPlugin, + gz::sim::System, + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemConfigure, + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemPreUpdate, + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemPostUpdate) +GZ_ADD_PLUGIN_ALIAS( + gz_ros2_control::GazeboSimROS2ControlPlugin, + "ign_ros2_control::IgnitionROS2ControlPlugin") +#else IGNITION_ADD_PLUGIN( - ign_ros2_control::IgnitionROS2ControlPlugin, + gz_ros2_control::GazeboSimROS2ControlPlugin, ignition::gazebo::System, - ign_ros2_control::IgnitionROS2ControlPlugin::ISystemConfigure, - ign_ros2_control::IgnitionROS2ControlPlugin::ISystemPreUpdate, - ign_ros2_control::IgnitionROS2ControlPlugin::ISystemPostUpdate) + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemConfigure, + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemPreUpdate, + gz_ros2_control::GazeboSimROS2ControlPlugin::ISystemPostUpdate) +IGNITION_ADD_PLUGIN_ALIAS( + gz_ros2_control::GazeboSimROS2ControlPlugin, + "ign_ros2_control::IgnitionROS2ControlPlugin") +#endif diff --git a/gz_ros2_control/src/gz_system.cpp b/gz_ros2_control/src/gz_system.cpp index 95244cba..0d07fb95 100644 --- a/gz_ros2_control/src/gz_system.cpp +++ b/gz_ros2_control/src/gz_system.cpp @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ign_ros2_control/ign_system.hpp" - -#include +#include "gz_ros2_control/gz_system.hpp" #include #include @@ -23,6 +21,28 @@ #include #include +#ifdef GZ_HEADERS +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define GZ_TRANSPORT_NAMESPACE gz::transport:: +#define GZ_MSGS_NAMESPACE gz::msgs:: +#else +#include + #include #include #include @@ -35,9 +55,10 @@ #include #include #include - #include - +#define GZ_TRANSPORT_NAMESPACE ignition::transport:: +#define GZ_MSGS_NAMESPACE ignition::msgs:: +#endif #include @@ -65,10 +86,10 @@ struct jointData double joint_effort_cmd; /// \brief handles to the joints from within Gazebo - ignition::gazebo::Entity sim_joint; + sim::Entity sim_joint; /// \brief Control method defined in the URDF for each joint. - ign_ros2_control::IgnitionSystemInterface::ControlMethod joint_control_method; + gz_ros2_control::GazeboSimSystemInterface::ControlMethod joint_control_method; }; struct MimicJoint @@ -89,16 +110,16 @@ class ImuData std::string topicName{}; /// \brief handles to the imu from within Gazebo - ignition::gazebo::Entity sim_imu_sensors_ = ignition::gazebo::kNullEntity; + sim::Entity sim_imu_sensors_ = sim::kNullEntity; /// \brief An array per IMU with 4 orientation, 3 angular velocity and 3 linear acceleration std::array imu_sensor_data_; /// \brief callback to get the IMU topic values - void OnIMU(const ignition::msgs::IMU & _msg); + void OnIMU(const GZ_MSGS_NAMESPACE IMU & _msg); }; -void ImuData::OnIMU(const ignition::msgs::IMU & _msg) +void ImuData::OnIMU(const GZ_MSGS_NAMESPACE IMU & _msg) { this->imu_sensor_data_[0] = _msg.orientation().x(); this->imu_sensor_data_[1] = _msg.orientation().y(); @@ -112,12 +133,12 @@ void ImuData::OnIMU(const ignition::msgs::IMU & _msg) this->imu_sensor_data_[9] = _msg.linear_acceleration().z(); } -class ign_ros2_control::IgnitionSystemPrivate +class gz_ros2_control::GazeboSimSystemPrivate { public: - IgnitionSystemPrivate() = default; + GazeboSimSystemPrivate() = default; - ~IgnitionSystemPrivate() = default; + ~GazeboSimSystemPrivate() = default; /// \brief Degrees od freedom. size_t n_dof_; @@ -138,13 +159,13 @@ class ign_ros2_control::IgnitionSystemPrivate /// \brief Entity component manager, ECM shouldn't be accessed outside those /// methods, otherwise the app will crash - ignition::gazebo::EntityComponentManager * ecm; + sim::EntityComponentManager * ecm; /// \brief controller update rate int * update_rate; - /// \brief Ignition communication node. - ignition::transport::Node node; + /// \brief Gazebo communication node. + GZ_TRANSPORT_NAMESPACE Node node; /// \brief mapping of mimicked joints to index of joint they mimic std::vector mimic_joints_; @@ -153,16 +174,17 @@ class ign_ros2_control::IgnitionSystemPrivate double position_proportional_gain_; }; -namespace ign_ros2_control +namespace gz_ros2_control { -bool IgnitionSystem::initSim( + +bool GazeboSimSystem::initSim( rclcpp::Node::SharedPtr & model_nh, - std::map & enableJoints, + std::map & enableJoints, const hardware_interface::HardwareInfo & hardware_info, - ignition::gazebo::EntityComponentManager & _ecm, + sim::EntityComponentManager & _ecm, int & update_rate) { - this->dataPtr = std::make_unique(); + this->dataPtr = std::make_unique(); this->dataPtr->last_update_sim_time_ros_ = rclcpp::Time(); this->nh_ = model_nh; @@ -195,31 +217,31 @@ bool IgnitionSystem::initSim( auto & joint_info = hardware_info.joints[j]; std::string joint_name = this->dataPtr->joints_[j].name = joint_info.name; - ignition::gazebo::Entity simjoint = enableJoints[joint_name]; + sim::Entity simjoint = enableJoints[joint_name]; this->dataPtr->joints_[j].sim_joint = simjoint; // Create joint position component if one doesn't exist if (!_ecm.EntityHasComponentType( simjoint, - ignition::gazebo::components::JointPosition().TypeId())) + sim::components::JointPosition().TypeId())) { - _ecm.CreateComponent(simjoint, ignition::gazebo::components::JointPosition()); + _ecm.CreateComponent(simjoint, sim::components::JointPosition()); } // Create joint velocity component if one doesn't exist if (!_ecm.EntityHasComponentType( simjoint, - ignition::gazebo::components::JointVelocity().TypeId())) + sim::components::JointVelocity().TypeId())) { - _ecm.CreateComponent(simjoint, ignition::gazebo::components::JointVelocity()); + _ecm.CreateComponent(simjoint, sim::components::JointVelocity()); } // Create joint force component if one doesn't exist if (!_ecm.EntityHasComponentType( simjoint, - ignition::gazebo::components::JointForce().TypeId())) + sim::components::JointForce().TypeId())) { - _ecm.CreateComponent(simjoint, ignition::gazebo::components::JointForce()); + _ecm.CreateComponent(simjoint, sim::components::JointForce()); } // Accept this joint and continue configuration @@ -349,7 +371,6 @@ bool IgnitionSystem::initSim( this->dataPtr->joints_[j].joint_velocity_cmd = initial_velocity; } } else if (joint_info.command_interfaces[i].name == "effort") { - this->dataPtr->joints_[j].joint_control_method |= EFFORT; RCLCPP_INFO_STREAM(this->nh_->get_logger(), "\t\t effort"); this->dataPtr->command_interfaces_.emplace_back( joint_name + suffix, @@ -374,7 +395,7 @@ bool IgnitionSystem::initSim( return true; } -void IgnitionSystem::registerSensors( +void GazeboSimSystem::registerSensors( const hardware_interface::HardwareInfo & hardware_info) { // Collect gazebo sensor handles @@ -389,17 +410,17 @@ void IgnitionSystem::registerSensors( // So we have resize only once the structures where the data will be stored, and we can safely // use pointers to the structures - this->dataPtr->ecm->Each( - [&](const ignition::gazebo::Entity & _entity, - const ignition::gazebo::components::Imu *, - const ignition::gazebo::components::Name * _name) -> bool + this->dataPtr->ecm->Each( + [&](const sim::Entity & _entity, + const sim::components::Imu *, + const sim::components::Name * _name) -> bool { auto imuData = std::make_shared(); RCLCPP_INFO_STREAM(this->nh_->get_logger(), "Loading sensor: " << _name->Data()); auto sensorTopicComp = this->dataPtr->ecm->Component< - ignition::gazebo::components::SensorTopic>(_entity); + sim::components::SensorTopic>(_entity); if (sensorTopicComp) { RCLCPP_INFO_STREAM(this->nh_->get_logger(), "Topic name: " << sensorTopicComp->Data()); } @@ -444,16 +465,16 @@ void IgnitionSystem::registerSensors( } CallbackReturn -IgnitionSystem::on_init(const hardware_interface::HardwareInfo & system_info) +GazeboSimSystem::on_init(const hardware_interface::HardwareInfo & actuator_info) { RCLCPP_WARN(this->nh_->get_logger(), "On init..."); - if (hardware_interface::SystemInterface::on_init(system_info) != CallbackReturn::SUCCESS) { + if (hardware_interface::SystemInterface::on_init(actuator_info) != CallbackReturn::SUCCESS) { return CallbackReturn::ERROR; } return CallbackReturn::SUCCESS; } -CallbackReturn IgnitionSystem::on_configure( +CallbackReturn GazeboSimSystem::on_configure( const rclcpp_lifecycle::State & /*previous_state*/) { RCLCPP_INFO( @@ -463,48 +484,48 @@ CallbackReturn IgnitionSystem::on_configure( } std::vector -IgnitionSystem::export_state_interfaces() +GazeboSimSystem::export_state_interfaces() { return std::move(this->dataPtr->state_interfaces_); } std::vector -IgnitionSystem::export_command_interfaces() +GazeboSimSystem::export_command_interfaces() { return std::move(this->dataPtr->command_interfaces_); } -CallbackReturn IgnitionSystem::on_activate(const rclcpp_lifecycle::State & previous_state) +CallbackReturn GazeboSimSystem::on_activate(const rclcpp_lifecycle::State & previous_state) { return CallbackReturn::SUCCESS; return hardware_interface::SystemInterface::on_activate(previous_state); } -CallbackReturn IgnitionSystem::on_deactivate(const rclcpp_lifecycle::State & previous_state) +CallbackReturn GazeboSimSystem::on_deactivate(const rclcpp_lifecycle::State & previous_state) { return CallbackReturn::SUCCESS; return hardware_interface::SystemInterface::on_deactivate(previous_state); } -hardware_interface::return_type IgnitionSystem::read( +hardware_interface::return_type GazeboSimSystem::read( const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) { for (unsigned int i = 0; i < this->dataPtr->joints_.size(); ++i) { // Get the joint velocity const auto * jointVelocity = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); - // TODO(ahcorde): Revisit this part ignitionrobotics/ign-physics#124 + // TODO(ahcorde): Revisit this part gazebosim/ign-physics#124 // Get the joint force // const auto * jointForce = - // _ecm.Component( + // _ecm.Component( // this->dataPtr->sim_joints_[j]); // Get the joint position const auto * jointPositions = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); this->dataPtr->joints_[i].joint_position = jointPositions->Data()[0]; @@ -515,7 +536,7 @@ hardware_interface::return_type IgnitionSystem::read( for (unsigned int i = 0; i < this->dataPtr->imus_.size(); ++i) { if (this->dataPtr->imus_[i]->topicName.empty()) { auto sensorTopicComp = this->dataPtr->ecm->Component< - ignition::gazebo::components::SensorTopic>(this->dataPtr->imus_[i]->sim_imu_sensors_); + sim::components::SensorTopic>(this->dataPtr->imus_[i]->sim_imu_sensors_); if (sensorTopicComp) { this->dataPtr->imus_[i]->topicName = sensorTopicComp->Data(); RCLCPP_INFO_STREAM( @@ -532,7 +553,7 @@ hardware_interface::return_type IgnitionSystem::read( } hardware_interface::return_type -IgnitionSystem::perform_command_mode_switch( +GazeboSimSystem::perform_command_mode_switch( const std::vector & start_interfaces, const std::vector & stop_interfaces) { @@ -578,23 +599,23 @@ IgnitionSystem::perform_command_mode_switch( return hardware_interface::return_type::OK; } -hardware_interface::return_type IgnitionSystem::write( +hardware_interface::return_type GazeboSimSystem::write( const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) { for (unsigned int i = 0; i < this->dataPtr->joints_.size(); ++i) { if (this->dataPtr->joints_[i].joint_control_method & VELOCITY) { - if (!this->dataPtr->ecm->Component( + if (!this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint)) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[i].sim_joint, - ignition::gazebo::components::JointVelocityCmd({0})); + sim::components::JointVelocityCmd({0})); } else { const auto jointVelCmd = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); - *jointVelCmd = ignition::gazebo::components::JointVelocityCmd( + *jointVelCmd = sim::components::JointVelocityCmd( {this->dataPtr->joints_[i].joint_velocity_cmd}); } } else if (this->dataPtr->joints_[i].joint_control_method & POSITION) { @@ -607,41 +628,41 @@ hardware_interface::return_type IgnitionSystem::write( double target_vel = -this->dataPtr->position_proportional_gain_ * error; auto vel = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); if (vel == nullptr) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[i].sim_joint, - ignition::gazebo::components::JointVelocityCmd({target_vel})); + sim::components::JointVelocityCmd({target_vel})); } else if (!vel->Data().empty()) { vel->Data()[0] = target_vel; } } else if (this->dataPtr->joints_[i].joint_control_method & EFFORT) { - if (!this->dataPtr->ecm->Component( + if (!this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint)) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[i].sim_joint, - ignition::gazebo::components::JointForceCmd({0})); + sim::components::JointForceCmd({0})); } else { const auto jointEffortCmd = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); - *jointEffortCmd = ignition::gazebo::components::JointForceCmd( + *jointEffortCmd = sim::components::JointForceCmd( {this->dataPtr->joints_[i].joint_effort_cmd}); } } else { // Fallback case is a velocity command of zero double target_vel = 0.0; auto vel = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[i].sim_joint); if (vel == nullptr) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[i].sim_joint, - ignition::gazebo::components::JointVelocityCmd({target_vel})); + sim::components::JointVelocityCmd({target_vel})); } else if (!vel->Data().empty()) { vel->Data()[0] = target_vel; } else if (!vel->Data().empty()) { @@ -656,11 +677,11 @@ hardware_interface::return_type IgnitionSystem::write( if (mimic_interface == "position") { // Get the joint position double position_mimicked_joint = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.mimicked_joint_index].sim_joint)->Data()[0]; double position_mimic_joint = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint)->Data()[0]; double position_error = @@ -669,13 +690,13 @@ hardware_interface::return_type IgnitionSystem::write( double velocity_sp = (-1.0) * position_error * (*this->dataPtr->update_rate); auto vel = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint); if (vel == nullptr) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint, - ignition::gazebo::components::JointVelocityCmd({velocity_sp})); + sim::components::JointVelocityCmd({velocity_sp})); } else if (!vel->Data().empty()) { vel->Data()[0] = velocity_sp; } @@ -683,20 +704,20 @@ hardware_interface::return_type IgnitionSystem::write( if (mimic_interface == "velocity") { // get the velocity of mimicked joint double velocity_mimicked_joint = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.mimicked_joint_index].sim_joint)->Data()[0]; - if (!this->dataPtr->ecm->Component( + if (!this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint)) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint, - ignition::gazebo::components::JointVelocityCmd({0})); + sim::components::JointVelocityCmd({0})); } else { const auto jointVelCmd = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint); - *jointVelCmd = ignition::gazebo::components::JointVelocityCmd( + *jointVelCmd = sim::components::JointVelocityCmd( {mimic_joint.multiplier * velocity_mimicked_joint}); } } @@ -704,19 +725,19 @@ hardware_interface::return_type IgnitionSystem::write( // TODO(ahcorde): Revisit this part ignitionrobotics/ign-physics#124 // Get the joint force // const auto * jointForce = - // _ecm.Component( + // _ecm.Component( // this->dataPtr->sim_joints_[j]); - if (!this->dataPtr->ecm->Component( + if (!this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint)) { this->dataPtr->ecm->CreateComponent( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint, - ignition::gazebo::components::JointForceCmd({0})); + sim::components::JointForceCmd({0})); } else { const auto jointEffortCmd = - this->dataPtr->ecm->Component( + this->dataPtr->ecm->Component( this->dataPtr->joints_[mimic_joint.joint_index].sim_joint); - *jointEffortCmd = ignition::gazebo::components::JointForceCmd( + *jointEffortCmd = sim::components::JointForceCmd( {mimic_joint.multiplier * this->dataPtr->joints_[mimic_joint.mimicked_joint_index].joint_effort}); } @@ -726,8 +747,8 @@ hardware_interface::return_type IgnitionSystem::write( return hardware_interface::return_type::OK; } -} // namespace ign_ros2_control +} // namespace gz_ros2_control #include "pluginlib/class_list_macros.hpp" // NOLINT PLUGINLIB_EXPORT_CLASS( - ign_ros2_control::IgnitionSystem, ign_ros2_control::IgnitionSystemInterface) + gz_ros2_control::GazeboSimSystem, gz_ros2_control::GazeboSimSystemInterface) diff --git a/gz_ros2_control_demos/CMakeLists.txt b/gz_ros2_control_demos/CMakeLists.txt index e1031c2e..6d106a44 100644 --- a/gz_ros2_control_demos/CMakeLists.txt +++ b/gz_ros2_control_demos/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.5.0) -project(ign_ros2_control_demos) +project(gz_ros2_control_demos) # Default to C11 if(NOT CMAKE_C_STANDARD) diff --git a/gz_ros2_control_demos/config/cartpole_controller_position.yaml b/gz_ros2_control_demos/config/cartpole_controller_position.yaml index f51d2fa5..835a8360 100644 --- a/gz_ros2_control_demos/config/cartpole_controller_position.yaml +++ b/gz_ros2_control_demos/config/cartpole_controller_position.yaml @@ -1,6 +1,6 @@ controller_manager: ros__parameters: - update_rate: 100 # Hz + update_rate: 1000 # Hz joint_trajectory_controller: type: joint_trajectory_controller/JointTrajectoryController diff --git a/gz_ros2_control_demos/launch/cart_example_effort.launch.py b/gz_ros2_control_demos/launch/cart_example_effort.launch.py index f6cb30eb..780975f8 100644 --- a/gz_ros2_control_demos/launch/cart_example_effort.launch.py +++ b/gz_ros2_control_demos/launch/cart_example_effort.launch.py @@ -33,10 +33,10 @@ def generate_launch_description(): # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_cart_effort.xacro.urdf') @@ -51,8 +51,8 @@ def generate_launch_description(): parameters=[params] ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -66,7 +66,7 @@ def generate_launch_description(): output='screen' ) - load_joint_trajectory_controller = ExecuteProcess( + load_joint_effort_controller = ExecuteProcess( cmd=['ros2', 'control', 'load_controller', '--set-state', 'active', 'effort_controllers'], output='screen' ) @@ -75,23 +75,23 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 3 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 3 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_broadcaster], ) ), RegisterEventHandler( event_handler=OnProcessExit( target_action=load_joint_state_broadcaster, - on_exit=[load_joint_trajectory_controller], + on_exit=[load_joint_effort_controller], ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/cart_example_position.launch.py b/gz_ros2_control_demos/launch/cart_example_position.launch.py index 71933645..3886d1b9 100644 --- a/gz_ros2_control_demos/launch/cart_example_position.launch.py +++ b/gz_ros2_control_demos/launch/cart_example_position.launch.py @@ -33,10 +33,10 @@ def generate_launch_description(): # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_cart_position.xacro.urdf') @@ -51,8 +51,8 @@ def generate_launch_description(): parameters=[params] ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -76,12 +76,12 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 4 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 4 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_broadcaster], ) ), @@ -92,7 +92,7 @@ def generate_launch_description(): ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/cart_example_velocity.launch.py b/gz_ros2_control_demos/launch/cart_example_velocity.launch.py index a8a95fbc..ce3d6269 100644 --- a/gz_ros2_control_demos/launch/cart_example_velocity.launch.py +++ b/gz_ros2_control_demos/launch/cart_example_velocity.launch.py @@ -33,10 +33,10 @@ def generate_launch_description(): # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_cart_velocity.xacro.urdf') @@ -51,8 +51,8 @@ def generate_launch_description(): parameters=[params] ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -66,7 +66,7 @@ def generate_launch_description(): output='screen' ) - load_joint_trajectory_controller = ExecuteProcess( + load_joint_velocity_controller = ExecuteProcess( cmd=['ros2', 'control', 'load_controller', '--set-state', 'active', 'velocity_controller'], output='screen' ) @@ -81,29 +81,29 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 4 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 4 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_broadcaster], ) ), RegisterEventHandler( event_handler=OnProcessExit( target_action=load_joint_state_broadcaster, - on_exit=[load_joint_trajectory_controller], + on_exit=[load_joint_velocity_controller], ) ), RegisterEventHandler( event_handler=OnProcessExit( - target_action=load_joint_trajectory_controller, + target_action=load_joint_velocity_controller, on_exit=[load_imu_sensor_broadcaster], ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/diff_drive_example.launch.py b/gz_ros2_control_demos/launch/diff_drive_example.launch.py index b12ef608..d47df421 100644 --- a/gz_ros2_control_demos/launch/diff_drive_example.launch.py +++ b/gz_ros2_control_demos/launch/diff_drive_example.launch.py @@ -32,10 +32,10 @@ def generate_launch_description(): # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_diff_drive.xacro.urdf') @@ -52,8 +52,8 @@ def generate_launch_description(): parameters=[params], ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -77,12 +77,12 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 4 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 4 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_controller], ) ), @@ -93,7 +93,7 @@ def generate_launch_description(): ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py b/gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py index 6018dc9d..ea44bf1c 100644 --- a/gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py +++ b/gz_ros2_control_demos/launch/gripper_mimic_joint_example.launch.py @@ -36,10 +36,10 @@ def generate_launch_description(): # Launch arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_gripper_mimic_joint.xacro.urdf') @@ -54,8 +54,8 @@ def generate_launch_description(): parameters=[params] ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -79,12 +79,12 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 4 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 4 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_broadcaster], ) ), @@ -95,7 +95,7 @@ def generate_launch_description(): ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/launch/tricycle_drive_example.launch.py b/gz_ros2_control_demos/launch/tricycle_drive_example.launch.py index ccc98f33..f23b8a75 100644 --- a/gz_ros2_control_demos/launch/tricycle_drive_example.launch.py +++ b/gz_ros2_control_demos/launch/tricycle_drive_example.launch.py @@ -32,10 +32,10 @@ def generate_launch_description(): # Launch Arguments use_sim_time = LaunchConfiguration('use_sim_time', default=True) - ignition_ros2_control_demos_path = os.path.join( - get_package_share_directory('ign_ros2_control_demos')) + gz_ros2_control_demos_path = os.path.join( + get_package_share_directory('gz_ros2_control_demos')) - xacro_file = os.path.join(ignition_ros2_control_demos_path, + xacro_file = os.path.join(gz_ros2_control_demos_path, 'urdf', 'test_tricycle_drive.xacro.urdf') @@ -52,8 +52,8 @@ def generate_launch_description(): parameters=[params], ) - ignition_spawn_entity = Node( - package='ros_ign_gazebo', + gz_spawn_entity = Node( + package='ros_gz_sim', executable='create', output='screen', arguments=['-string', doc.toxml(), @@ -77,12 +77,12 @@ def generate_launch_description(): # Launch gazebo environment IncludeLaunchDescription( PythonLaunchDescriptionSource( - [os.path.join(get_package_share_directory('ros_ign_gazebo'), - 'launch', 'ign_gazebo.launch.py')]), - launch_arguments=[('ign_args', [' -r -v 4 empty.sdf'])]), + [os.path.join(get_package_share_directory('ros_gz_sim'), + 'launch', 'gz_sim.launch.py')]), + launch_arguments=[('gz_args', [' -r -v 4 empty.sdf'])]), RegisterEventHandler( event_handler=OnProcessExit( - target_action=ignition_spawn_entity, + target_action=gz_spawn_entity, on_exit=[load_joint_state_controller], ) ), @@ -93,7 +93,7 @@ def generate_launch_description(): ) ), node_robot_state_publisher, - ignition_spawn_entity, + gz_spawn_entity, # Launch Arguments DeclareLaunchArgument( 'use_sim_time', diff --git a/gz_ros2_control_demos/package.xml b/gz_ros2_control_demos/package.xml index 826c70e3..828bebfb 100644 --- a/gz_ros2_control_demos/package.xml +++ b/gz_ros2_control_demos/package.xml @@ -1,8 +1,8 @@ - - ign_ros2_control_demos - 0.6.1 - ign_ros2_control_demos + + gz_ros2_control_demos + 0.5.0 + gz_ros2_control_demos Alejandro Hernandez @@ -27,7 +27,7 @@ effort_controllers geometry_msgs hardware_interface - ign_ros2_control + gz_ros2_control imu_sensor_broadcaster joint_state_broadcaster joint_trajectory_controller @@ -36,7 +36,9 @@ ros2launch rclcpp robot_state_publisher - ros_ign_gazebo + ros_gz_sim + ros_gz_sim + ros_ign_gazebo ros2controlcli std_msgs velocity_controllers diff --git a/gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf index 6fa0af31..cd70aa02 100644 --- a/gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_cart_effort.xacro.urdf @@ -43,9 +43,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -82,8 +82,8 @@ - - $(find ign_ros2_control_demos)/config/cartpole_controller_effort.yaml + + $(find gz_ros2_control_demos)/config/cartpole_controller_effort.yaml diff --git a/gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf index eeff47ec..a7faaac7 100644 --- a/gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_cart_position.xacro.urdf @@ -12,6 +12,12 @@ + + + + + + @@ -24,6 +30,12 @@ + + + + + + @@ -43,9 +55,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -53,7 +65,7 @@ 15 - -5.0 + 5.0 @@ -82,8 +94,8 @@ - - $(find ign_ros2_control_demos)/config/cartpole_controller_position.yaml + + $(find gz_ros2_control_demos)/config/cartpole_controller_position.yaml diff --git a/gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf b/gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf index 59a960a1..71b0c93f 100644 --- a/gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_cart_velocity.xacro.urdf @@ -4,6 +4,7 @@ + @@ -66,7 +67,11 @@ - + + + + + @@ -111,9 +116,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -160,11 +165,11 @@ - - $(find ign_ros2_control_demos)/config/cartpole_controller_velocity.yaml + + $(find gz_ros2_control_demos)/config/cartpole_controller_velocity.yaml diff --git a/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf index e608ed9e..7b77b090 100644 --- a/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_diff_drive.xacro.urdf @@ -132,9 +132,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -156,8 +156,8 @@ - - $(find ign_ros2_control_demos)/config/diff_drive_controller_velocity.yaml + + $(find gz_ros2_control_demos)/config/diff_drive_controller_velocity.yaml diff --git a/gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf b/gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf index 8e0074f4..555bd493 100644 --- a/gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_gripper_mimic_joint.xacro.urdf @@ -11,9 +11,16 @@ + + + + + + + - + @@ -27,8 +34,9 @@ - - + + + @@ -42,8 +50,9 @@ - - + + + @@ -66,9 +75,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -89,8 +98,8 @@ - - $(find ign_ros2_control_demos)/config/gripper_controller.yaml + + $(find gz_ros2_control_demos)/config/gripper_controller.yaml diff --git a/gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf b/gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf index 7d58323d..c0971c1a 100644 --- a/gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf +++ b/gz_ros2_control_demos/urdf/test_tricycle_drive.xacro.urdf @@ -150,9 +150,9 @@ - + - ign_ros2_control/IgnitionSystem + gz_ros2_control/GazeboSimSystem @@ -167,8 +167,8 @@ - - $(find ign_ros2_control_demos)/config/tricycle_drive_controller.yaml + + $(find gz_ros2_control_demos)/config/tricycle_drive_controller.yaml diff --git a/img/gz_ros2_control.gif b/img/gz_ros2_control.gif new file mode 100644 index 0000000000000000000000000000000000000000..65664bde015eaa50ab537f501ef638620f1deb9b GIT binary patch literal 83704 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�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 Ns+zwnj&Ck({tw$v8WR8j literal 0 HcmV?d00001 From ca6f44a39b9797784bce11c7e84db168d3c5e375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Thu, 23 Feb 2023 11:55:18 +0100 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Dr. Denis --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 809313b7..8a8ed1c5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Gazebo ros2 control CI +name: Gazebo-Sim ros2 control CI on: pull_request: