From f21eca594c222f291a466edae941df1ae58acfb9 Mon Sep 17 00:00:00 2001 From: Ilia Baranov <90713890+iliabaranov@users.noreply.github.com> Date: Sat, 23 Dec 2023 17:35:29 -0800 Subject: [PATCH 01/16] Removed seq from Header, as it is no longer supported in ROS2 --- AUTHORS.rst | 1 + src/roslibpy/core.py | 3 +-- tests/test_core.py | 4 ++-- tests/test_topic.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index b5f29ff..43af378 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,3 +10,4 @@ Authors * Hiroyuki Obinata `@obi-t4 `_ * Pedro Pereira `@MisterOwlPT `_ * Domenic Rodriguez `@DomenicP `_ +* Ilia Baranov `@iliabaranov `_ diff --git a/src/roslibpy/core.py b/src/roslibpy/core.py index 90ca6f5..9b8faf7 100644 --- a/src/roslibpy/core.py +++ b/src/roslibpy/core.py @@ -50,9 +50,8 @@ def __init__(self, values=None): class Header(UserDict): """Represents a message header of the ROS type std_msgs/Header.""" - def __init__(self, seq=None, stamp=None, frame_id=None): + def __init__(self, stamp=None, frame_id=None): self.data = {} - self.data["seq"] = seq self.data["stamp"] = Time(stamp["secs"], stamp["nsecs"]) if stamp else None self.data["frame_id"] = frame_id diff --git a/tests/test_core.py b/tests/test_core.py index 580414c..cbe5f32 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -27,14 +27,14 @@ def test_is_zero(): def test_header_ctor_supports_time(): - header = Header(seq=1, stamp=Time.from_sec(REF_FLOAT_SECS_TIME)) + header = Header(stamp=Time.from_sec(REF_FLOAT_SECS_TIME)) assert header["stamp"]["secs"] == 1610122759 assert header["stamp"]["secs"] == header["stamp"].secs assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME def test_header_ctor_supports_dict(): - header = Header(seq=1, stamp=dict(secs=1610122759, nsecs=677661895)) + header = Header(stamp=dict(secs=1610122759, nsecs=677661895)) assert header["stamp"]["secs"] == 1610122759 assert header["stamp"]["secs"] == header["stamp"].secs assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME diff --git a/tests/test_topic.py b/tests/test_topic.py index 97ba430..822edcb 100644 --- a/tests/test_topic.py +++ b/tests/test_topic.py @@ -69,7 +69,7 @@ def receive_message(message): def start_sending(): for i in range(3): - msg = dict(header=Header(seq=i, stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) + msg = dict(header=Header(stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) publisher.publish(Message(msg)) time.sleep(0.1) From 3a250477eb8e6b40b7ca7f8987d382d12063ca93 Mon Sep 17 00:00:00 2001 From: Ilia Baranov <90713890+iliabaranov@users.noreply.github.com> Date: Sat, 23 Dec 2023 17:41:12 -0800 Subject: [PATCH 02/16] Added note to changelog: Removed seq from Header, as it is no longer supported in ROS2 --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e915c28..341e7cf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,8 @@ Unreleased **Fixed** +* Removed seq field from the header message type, as this is no longer supported in ROS2 + **Deprecated** **Removed** From 8ee40dad983bbedf6e82465454eb88a8c4fc6baf Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 01:41:57 +0200 Subject: [PATCH 03/16] Update changelog --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 341e7cf..2a36fb1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,12 +12,12 @@ Unreleased **Added** +* Added a ROS2-compatible header class in ``roslibpy.ros2.Header``. + **Changed** **Fixed** -* Removed seq field from the header message type, as this is no longer supported in ROS2 - **Deprecated** **Removed** From 4c0b620bfeea98f9410bf381e66ab0e08c726a31 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 01:56:08 +0200 Subject: [PATCH 04/16] Tentative ROS1/ROS2 test setup --- .github/workflows/build.yml | 67 ---------------------- docker/{ => ros1}/Dockerfile | 0 docker/{ => ros1}/integration-tests.launch | 0 docker/{ => ros1}/ros_entrypoint.sh | 0 docker/ros2/Dockerfile | 21 +++++++ docker/ros2/integration-tests.launch | 6 ++ docker/ros2/ros_entrypoint.sh | 6 ++ 7 files changed, 33 insertions(+), 67 deletions(-) delete mode 100644 .github/workflows/build.yml rename docker/{ => ros1}/Dockerfile (100%) rename docker/{ => ros1}/integration-tests.launch (100%) rename docker/{ => ros1}/ros_entrypoint.sh (100%) mode change 100755 => 100644 create mode 100644 docker/ros2/Dockerfile create mode 100644 docker/ros2/integration-tests.launch create mode 100644 docker/ros2/ros_entrypoint.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 5f8c180..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: build - -on: - push: - branches: - - main - tags: - - 'v*' - pull_request: - branches: - - main - -jobs: - build-cpython: - runs-on: ${{ matrix.os }} - strategy: - matrix: - name: [ - "ubuntu-py37", - "ubuntu-py38", - "ubuntu-py39", - "ubuntu-py310", - "ubuntu-py311", - ] - include: - - name: "ubuntu-py37" - os: ubuntu-latest - python-version: "3.7" - - name: "ubuntu-py38" - os: ubuntu-latest - python-version: "3.8" - - name: "ubuntu-py39" - os: ubuntu-latest - python-version: "3.9" - - name: "ubuntu-py310" - os: ubuntu-latest - python-version: "3.10" - - name: "ubuntu-py311" - os: ubuntu-latest - python-version: "3.11" - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install wheel - - name: Install - run: | - python -m pip install --no-cache-dir -r requirements-dev.txt - - name: Set up docker containers - run: | - docker build -t gramaziokohler/rosbridge:integration_tests ./docker - docker run -d -p 9090:9090 --name rosbridge gramaziokohler/rosbridge:integration_tests /bin/bash -c "roslaunch /integration-tests.launch" - docker ps -a - - name: Run linter - run: | - invoke check - - name: Run tests - run: | - pytest - - name: Tear down docker containers - run: | - docker rm -f rosbridge diff --git a/docker/Dockerfile b/docker/ros1/Dockerfile similarity index 100% rename from docker/Dockerfile rename to docker/ros1/Dockerfile diff --git a/docker/integration-tests.launch b/docker/ros1/integration-tests.launch similarity index 100% rename from docker/integration-tests.launch rename to docker/ros1/integration-tests.launch diff --git a/docker/ros_entrypoint.sh b/docker/ros1/ros_entrypoint.sh old mode 100755 new mode 100644 similarity index 100% rename from docker/ros_entrypoint.sh rename to docker/ros1/ros_entrypoint.sh diff --git a/docker/ros2/Dockerfile b/docker/ros2/Dockerfile new file mode 100644 index 0000000..5665ac5 --- /dev/null +++ b/docker/ros2/Dockerfile @@ -0,0 +1,21 @@ +FROM ros:iron +LABEL maintainer "Gonzalo Casas " + +SHELL ["/bin/bash","-c"] + +# Install rosbridge +RUN apt-get update && apt-get install -y \ + ros-iron-rosbridge-suite \ + ros-iron-tf2-web-republisher \ + ros-iron-ros-tutorials \ + ros-iron-actionlib-tutorials \ + --no-install-recommends \ + # Clear apt-cache to reduce image size + && rm -rf /var/lib/apt/lists/* + +# Copy entrypoint +COPY ./integration-tests.launch / + +EXPOSE 9090 + +CMD ["bash"] diff --git a/docker/ros2/integration-tests.launch b/docker/ros2/integration-tests.launch new file mode 100644 index 0000000..e28730b --- /dev/null +++ b/docker/ros2/integration-tests.launch @@ -0,0 +1,6 @@ + + + + + + diff --git a/docker/ros2/ros_entrypoint.sh b/docker/ros2/ros_entrypoint.sh new file mode 100644 index 0000000..28bc65a --- /dev/null +++ b/docker/ros2/ros_entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +# setup ros2 environment +source "/opt/ros/$ROS_DISTRO/setup.bash" -- +exec "$@" From 6864386bd882f317f6e94a15e7c82736345d4dea Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 08:45:22 +0200 Subject: [PATCH 05/16] Add some notes about ROS2 support --- README.rst | 2 ++ docs/index.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.rst b/README.rst index c9b9ff1..bdeabfb 100644 --- a/README.rst +++ b/README.rst @@ -44,6 +44,8 @@ local ROS environment, allowing usage from platforms other than Linux. The API of **roslibpy** is modeled to closely match that of `roslibjs`_. +ROS1 is fully supported. ROS2 support is still in progress. + Main features ------------- diff --git a/docs/index.rst b/docs/index.rst index 7d074c2..5736c82 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,8 @@ local ROS environment, allowing usage from platforms other than Linux. The API of **roslibpy** is modeled to closely match that of `roslibjs `_. +ROS1 is fully supported. ROS2 support is still in progress. + ======== Contents ======== From 6cd7cd7a88ffa8837cb635378a556d13e332b15a Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 08:46:05 +0200 Subject: [PATCH 06/16] Make it a non-breaking change --- src/roslibpy/__init__.py | 13 +++++++++++++ src/roslibpy/core.py | 9 +++++++-- src/roslibpy/ros2/__init__.py | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/roslibpy/ros2/__init__.py diff --git a/src/roslibpy/__init__.py b/src/roslibpy/__init__.py index 76c39b5..201890a 100644 --- a/src/roslibpy/__init__.py +++ b/src/roslibpy/__init__.py @@ -41,6 +41,19 @@ Main ROS concepts ================= +ROS1 vs ROS2 +------------ + +This library has been tested to work with ROS1. ROS2 should work, but it is still +in the works. + +One area in which ROS1 and ROS2 differ is in the header interface. To use ROS2, use +the header defined in the `roslibpy.ros2` module. + +.. autoclass:: roslibpy.ros2.Header + :members: + + Topics ------ diff --git a/src/roslibpy/core.py b/src/roslibpy/core.py index 9b8faf7..6424155 100644 --- a/src/roslibpy/core.py +++ b/src/roslibpy/core.py @@ -48,10 +48,15 @@ def __init__(self, values=None): class Header(UserDict): - """Represents a message header of the ROS type std_msgs/Header.""" + """Represents a message header of the ROS type std_msgs/Header. - def __init__(self, stamp=None, frame_id=None): + This header is only compatible with ROS1. For ROS2 headers, use :class:`roslibpy.ros2.Header`. + + """ + + def __init__(self, seq=None, stamp=None, frame_id=None): self.data = {} + self.data["seq"] = seq self.data["stamp"] = Time(stamp["secs"], stamp["nsecs"]) if stamp else None self.data["frame_id"] = frame_id diff --git a/src/roslibpy/ros2/__init__.py b/src/roslibpy/ros2/__init__.py new file mode 100644 index 0000000..b3b1ed1 --- /dev/null +++ b/src/roslibpy/ros2/__init__.py @@ -0,0 +1,22 @@ +# Python 2/3 compatibility import list +try: + from collections import UserDict +except ImportError: + from UserDict import UserDict + +from roslibpy import Time +from roslibpy import Header as ROS1Header + +__all__ = [ + "Header", +] + + +class Header(ROS1Header): + """Represents a message header of the ROS type std_msgs/Header.""" + + def __init__(self, stamp=None, frame_id=None): + super(Header, self).__init__(stamp=stamp, frame_id=frame_id) + self.data["stamp"] = Time(stamp["secs"], stamp["nsecs"]) if stamp else None + self.data["frame_id"] = frame_id + del self.data["seq"] From 22af99767a005f46a9dd240def93ef74fd4571e9 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 08:50:33 +0200 Subject: [PATCH 07/16] Add to docs --- docs/reference/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/index.rst b/docs/reference/index.rst index a15ca8b..9556e53 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -10,3 +10,4 @@ API Reference .. automodule:: roslibpy .. automodule:: roslibpy.actionlib .. automodule:: roslibpy.tf +.. automodule:: roslibpy.ros2 From d53271df07283a9952f011556b20963ab9e7fd4a Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 10:58:45 +0200 Subject: [PATCH 08/16] Add ROS2 to CI and add a small test for it --- .github/workflows/build-ros1.yml | 59 +++++++++++++++++ .github/workflows/build-ros2.yml | 59 +++++++++++++++++ docker/ros2/Dockerfile | 6 +- docker/ros2/integration-tests.launch | 4 +- tests/{ => ros1}/test_actionlib.py | 0 tests/{ => ros1}/test_core.py | 0 tests/{ => ros1}/test_param.py | 0 tests/{ => ros1}/test_ros.py | 0 tests/{ => ros1}/test_rosapi.py | 0 tests/{ => ros1}/test_service.py | 0 tests/{ => ros1}/test_tf.py | 0 tests/{ => ros1}/test_topic.py | 2 +- tests/ros2/test_topic_ros2.py | 94 ++++++++++++++++++++++++++++ 13 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/build-ros1.yml create mode 100644 .github/workflows/build-ros2.yml rename tests/{ => ros1}/test_actionlib.py (100%) rename tests/{ => ros1}/test_core.py (100%) rename tests/{ => ros1}/test_param.py (100%) rename tests/{ => ros1}/test_ros.py (100%) rename tests/{ => ros1}/test_rosapi.py (100%) rename tests/{ => ros1}/test_service.py (100%) rename tests/{ => ros1}/test_tf.py (100%) rename tests/{ => ros1}/test_topic.py (98%) create mode 100644 tests/ros2/test_topic_ros2.py diff --git a/.github/workflows/build-ros1.yml b/.github/workflows/build-ros1.yml new file mode 100644 index 0000000..cb849a0 --- /dev/null +++ b/.github/workflows/build-ros1.yml @@ -0,0 +1,59 @@ +name: build + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + +jobs: + build-cpython: + runs-on: ${{ matrix.os }} + strategy: + matrix: + name: [ + "ubuntu-py39", + "ubuntu-py310", + "ubuntu-py311", + ] + include: + - name: "ubuntu-py39" + os: ubuntu-latest + python-version: "3.9" + - name: "ubuntu-py310" + os: ubuntu-latest + python-version: "3.10" + - name: "ubuntu-py311" + os: ubuntu-latest + python-version: "3.11" + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install wheel + - name: Install + run: | + python -m pip install --no-cache-dir -r requirements-dev.txt + - name: Set up docker containers + run: | + docker build -t gramaziokohler/rosbridge:integration_tests_ros1 ./docker/ros1 + docker run -d -p 9090:9090 --name rosbridge gramaziokohler/rosbridge:integration_tests_ros1 /bin/bash -c "roslaunch /integration-tests.launch" + docker ps -a + - name: Run linter + run: | + invoke check + - name: Run tests + run: | + pytest tests/ros1 + - name: Tear down docker containers + run: | + docker rm -f rosbridge diff --git a/.github/workflows/build-ros2.yml b/.github/workflows/build-ros2.yml new file mode 100644 index 0000000..95aa6dd --- /dev/null +++ b/.github/workflows/build-ros2.yml @@ -0,0 +1,59 @@ +name: build + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + +jobs: + build-cpython: + runs-on: ${{ matrix.os }} + strategy: + matrix: + name: [ + "ubuntu-py39", + "ubuntu-py310", + "ubuntu-py311", + ] + include: + - name: "ubuntu-py39" + os: ubuntu-latest + python-version: "3.9" + - name: "ubuntu-py310" + os: ubuntu-latest + python-version: "3.10" + - name: "ubuntu-py311" + os: ubuntu-latest + python-version: "3.11" + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install wheel + - name: Install + run: | + python -m pip install --no-cache-dir -r requirements-dev.txt + - name: Set up docker containers + run: | + docker build -t gramaziokohler/rosbridge:integration_tests_ros2 ./docker/ros2 + docker run -d -p 9090:9090 --name rosbridge gramaziokohler/rosbridge:integration_tests_ros2 /bin/bash -c "ros2 launch /integration-tests.launch" + docker ps -a + - name: Run linter + run: | + invoke check + - name: Run tests + run: | + pytest tests/ros2 + - name: Tear down docker containers + run: | + docker rm -f rosbridge diff --git a/docker/ros2/Dockerfile b/docker/ros2/Dockerfile index 5665ac5..b4a0148 100644 --- a/docker/ros2/Dockerfile +++ b/docker/ros2/Dockerfile @@ -6,9 +6,9 @@ SHELL ["/bin/bash","-c"] # Install rosbridge RUN apt-get update && apt-get install -y \ ros-iron-rosbridge-suite \ - ros-iron-tf2-web-republisher \ - ros-iron-ros-tutorials \ - ros-iron-actionlib-tutorials \ + # ros-iron-tf2-web-republisher \ + # ros-iron-ros-tutorials \ + # ros-iron-actionlib-tutorials \ --no-install-recommends \ # Clear apt-cache to reduce image size && rm -rf /var/lib/apt/lists/* diff --git a/docker/ros2/integration-tests.launch b/docker/ros2/integration-tests.launch index e28730b..1aedc6d 100644 --- a/docker/ros2/integration-tests.launch +++ b/docker/ros2/integration-tests.launch @@ -1,6 +1,4 @@ - - - + diff --git a/tests/test_actionlib.py b/tests/ros1/test_actionlib.py similarity index 100% rename from tests/test_actionlib.py rename to tests/ros1/test_actionlib.py diff --git a/tests/test_core.py b/tests/ros1/test_core.py similarity index 100% rename from tests/test_core.py rename to tests/ros1/test_core.py diff --git a/tests/test_param.py b/tests/ros1/test_param.py similarity index 100% rename from tests/test_param.py rename to tests/ros1/test_param.py diff --git a/tests/test_ros.py b/tests/ros1/test_ros.py similarity index 100% rename from tests/test_ros.py rename to tests/ros1/test_ros.py diff --git a/tests/test_rosapi.py b/tests/ros1/test_rosapi.py similarity index 100% rename from tests/test_rosapi.py rename to tests/ros1/test_rosapi.py diff --git a/tests/test_service.py b/tests/ros1/test_service.py similarity index 100% rename from tests/test_service.py rename to tests/ros1/test_service.py diff --git a/tests/test_tf.py b/tests/ros1/test_tf.py similarity index 100% rename from tests/test_tf.py rename to tests/ros1/test_tf.py diff --git a/tests/test_topic.py b/tests/ros1/test_topic.py similarity index 98% rename from tests/test_topic.py rename to tests/ros1/test_topic.py index 822edcb..74e04c8 100644 --- a/tests/test_topic.py +++ b/tests/ros1/test_topic.py @@ -68,7 +68,7 @@ def receive_message(message): context["wait"].set() def start_sending(): - for i in range(3): + for _ in range(3): msg = dict(header=Header(stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) publisher.publish(Message(msg)) time.sleep(0.1) diff --git a/tests/ros2/test_topic_ros2.py b/tests/ros2/test_topic_ros2.py new file mode 100644 index 0000000..b899176 --- /dev/null +++ b/tests/ros2/test_topic_ros2.py @@ -0,0 +1,94 @@ +from __future__ import print_function + +import threading +import time + +from roslibpy import Message, Ros, Time, Topic +from roslibpy.ros2 import Header + + +def test_topic_pubsub(): + context = dict(wait=threading.Event(), counter=0) + + ros = Ros("127.0.0.1", 9090) + ros.run() + + listener = Topic(ros, "/chatter", "std_msgs/String") + publisher = Topic(ros, "/chatter", "std_msgs/String") + + def receive_message(message): + context["counter"] += 1 + assert message["data"] == "hello world", "Unexpected message content" + + if context["counter"] == 3: + listener.unsubscribe() + context["wait"].set() + + def start_sending(): + while True: + if context["counter"] >= 3: + break + publisher.publish(Message({"data": "hello world"})) + time.sleep(0.1) + publisher.unadvertise() + + def start_receiving(): + listener.subscribe(receive_message) + + t1 = threading.Thread(target=start_receiving) + t2 = threading.Thread(target=start_sending) + + t1.start() + t2.start() + + if not context["wait"].wait(10): + raise Exception + + t1.join() + t2.join() + + assert context["counter"] >= 3, "Expected at least 3 messages but got " + str(context["counter"]) + ros.close() + + +def test_topic_with_header(): + context = dict(wait=threading.Event()) + + ros = Ros("127.0.0.1", 9090) + ros.run() + + listener = Topic(ros, "/points", "geometry_msgs/PointStamped") + publisher = Topic(ros, "/points", "geometry_msgs/PointStamped") + + def receive_message(message): + assert message["header"]["frame_id"] == "base" + assert message["point"]["x"] == 0.0 + assert message["point"]["y"] == 1.0 + assert message["point"]["z"] == 2.0 + listener.unsubscribe() + context["wait"].set() + + def start_sending(): + for _ in range(3): + msg = dict(header=Header(stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) + publisher.publish(Message(msg)) + time.sleep(0.1) + + publisher.unadvertise() + + def start_receiving(): + listener.subscribe(receive_message) + + t1 = threading.Thread(target=start_receiving) + t2 = threading.Thread(target=start_sending) + + t1.start() + t2.start() + + if not context["wait"].wait(10): + raise Exception + + t1.join() + t2.join() + + ros.close() From 3a61d0ef554fa7c94a300c65401019aa20e3e513 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:06:57 +0200 Subject: [PATCH 09/16] Add more tests --- tests/ros2/test_core.py | 59 ++++++++++++ tests/ros2/test_ros.py | 95 +++++++++++++++++++ tests/ros2/test_rosapi.py | 47 +++++++++ .../{test_topic_ros2.py => test_topic.py} | 0 4 files changed, 201 insertions(+) create mode 100644 tests/ros2/test_core.py create mode 100644 tests/ros2/test_ros.py create mode 100644 tests/ros2/test_rosapi.py rename tests/ros2/{test_topic_ros2.py => test_topic.py} (100%) diff --git a/tests/ros2/test_core.py b/tests/ros2/test_core.py new file mode 100644 index 0000000..cbe5f32 --- /dev/null +++ b/tests/ros2/test_core.py @@ -0,0 +1,59 @@ +import pytest + +from roslibpy import Header, Time + +REF_FLOAT_SECS_TIME = 1610122759.677662 + + +def test_time_from_sec_based_on_time_module(): + t = Time.from_sec(REF_FLOAT_SECS_TIME) + assert t.secs == 1610122759 + assert t.nsecs == 677661895 + + +def test_to_nsec(): + t = Time.from_sec(REF_FLOAT_SECS_TIME) + assert t.to_nsec() == 1610122759677661895 + + +def test_to_sec(): + t = Time.from_sec(REF_FLOAT_SECS_TIME) + assert t.to_sec() == REF_FLOAT_SECS_TIME + + +def test_is_zero(): + assert Time(0, 0).is_zero() + assert Time(1, 0).is_zero() is False + + +def test_header_ctor_supports_time(): + header = Header(stamp=Time.from_sec(REF_FLOAT_SECS_TIME)) + assert header["stamp"]["secs"] == 1610122759 + assert header["stamp"]["secs"] == header["stamp"].secs + assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME + + +def test_header_ctor_supports_dict(): + header = Header(stamp=dict(secs=1610122759, nsecs=677661895)) + assert header["stamp"]["secs"] == 1610122759 + assert header["stamp"]["secs"] == header["stamp"].secs + assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME + + +def test_time_accepts_only_ints(): + with pytest.raises(ValueError): + Time(1.3, 1.0) + with pytest.raises(ValueError): + Time(100.0, 3.1) + + t = Time(110.0, 0.0) + assert t.secs == 110 + assert t.nsecs == 0 + + +def test_time_properties_are_readonly(): + t = Time.now() + with pytest.raises(AttributeError): + t.secs = 10 + with pytest.raises(AttributeError): + t.nsecs = 10 diff --git a/tests/ros2/test_ros.py b/tests/ros2/test_ros.py new file mode 100644 index 0000000..aef72c5 --- /dev/null +++ b/tests/ros2/test_ros.py @@ -0,0 +1,95 @@ +from __future__ import print_function + +import threading +import time + +from roslibpy import Ros + +host = "127.0.0.1" +port = 9090 +url = "ws://%s:%d" % (host, port) + + +def test_reconnect_does_not_trigger_on_client_close(): + ros = Ros(host, port) + ros.run() + + assert ros.is_connected, "ROS initially connected" + time.sleep(0.5) + event = threading.Event() + ros.on("close", lambda m: event.set()) + ros.close() + event.wait(5) + + assert not ros.is_connected, "Successful disconnect" + assert not ros.is_connecting, "Not trying to re-connect" + + +def test_connection(): + ros = Ros(host, port) + ros.run() + assert ros.is_connected + ros.close() + + +def test_url_connection(): + ros = Ros(url) + ros.run() + assert ros.is_connected + ros.close() + + +def test_closing_event(): + ros = Ros(url) + ros.run() + ctx = dict(closing_event_called=False, was_still_connected=False) + + def handle_closing(): + ctx["closing_event_called"] = True + ctx["was_still_connected"] = ros.is_connected + time.sleep(1.5) + + ts_start = time.time() + ros.on("closing", handle_closing) + ros.close() + ts_end = time.time() + closing_was_handled_synchronously_before_close = ts_end - ts_start >= 1.5 + + assert ctx["closing_event_called"] + assert ctx["was_still_connected"] + assert closing_was_handled_synchronously_before_close + + +def test_multithreaded_connect_disconnect(): + CONNECTIONS = 30 + clients = [] + + def connect(clients): + ros = Ros(url) + ros.run() + clients.append(ros) + + # First connect all + threads = [] + for _ in range(CONNECTIONS): + thread = threading.Thread(target=connect, args=(clients,)) + thread.daemon = False + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + # Assert connection status + for ros in clients: + assert ros.is_connected + + # Now disconnect all + for ros in clients: + ros.close() + + time.sleep(0.5) + + # Assert connection status + for ros in clients: + assert not ros.is_connected diff --git a/tests/ros2/test_rosapi.py b/tests/ros2/test_rosapi.py new file mode 100644 index 0000000..2f37ac1 --- /dev/null +++ b/tests/ros2/test_rosapi.py @@ -0,0 +1,47 @@ +import threading + +import pytest + +from roslibpy import Ros + +host = "127.0.0.1" +port = 9090 +url = "ws://%s:%d" % (host, port) + + +def test_rosapi_topics(): + context = dict(wait=threading.Event(), result=None) + ros = Ros(host, port) + ros.run() + + def callback(topic_list): + context["result"] = topic_list + context["wait"].set() + + ros.get_topics(callback) + if not context["wait"].wait(5): + raise Exception + + assert "/rosout" in context["result"]["topics"] + ros.close() + + +def test_rosapi_topics_blocking(): + ros = Ros(host, port) + ros.run() + topic_list = ros.get_topics() + + print(topic_list) + assert "/rosout" in topic_list + + ros.close() + + +def test_connection_fails_when_missing_port(): + with pytest.raises(Exception): + Ros(host) + + +def test_connection_fails_when_schema_not_ws(): + with pytest.raises(Exception): + Ros("http://%s:%d" % (host, port)) diff --git a/tests/ros2/test_topic_ros2.py b/tests/ros2/test_topic.py similarity index 100% rename from tests/ros2/test_topic_ros2.py rename to tests/ros2/test_topic.py From 53e397d7e85792fa30a5a9b66965bb5b083f63b7 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:09:16 +0200 Subject: [PATCH 10/16] lint --- src/roslibpy/ros2/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/roslibpy/ros2/__init__.py b/src/roslibpy/ros2/__init__.py index b3b1ed1..2308388 100644 --- a/src/roslibpy/ros2/__init__.py +++ b/src/roslibpy/ros2/__init__.py @@ -1,9 +1,3 @@ -# Python 2/3 compatibility import list -try: - from collections import UserDict -except ImportError: - from UserDict import UserDict - from roslibpy import Time from roslibpy import Header as ROS1Header From fbc9fbb84ec3bc0b2de4079522dc8530df241dea Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:09:55 +0200 Subject: [PATCH 11/16] update ci --- .github/workflows/build-ros1.yml | 2 +- .github/workflows/build-ros2.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ros1.yml b/.github/workflows/build-ros1.yml index cb849a0..f11e138 100644 --- a/.github/workflows/build-ros1.yml +++ b/.github/workflows/build-ros1.yml @@ -11,7 +11,7 @@ on: - main jobs: - build-cpython: + build-ros1: runs-on: ${{ matrix.os }} strategy: matrix: diff --git a/.github/workflows/build-ros2.yml b/.github/workflows/build-ros2.yml index 95aa6dd..29a94ab 100644 --- a/.github/workflows/build-ros2.yml +++ b/.github/workflows/build-ros2.yml @@ -11,7 +11,7 @@ on: - main jobs: - build-cpython: + build-ros2: runs-on: ${{ matrix.os }} strategy: matrix: From bb19a29f4fbaf280f2217cfed803f82c7f60fe38 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:17:17 +0200 Subject: [PATCH 12/16] more lint --- src/roslibpy/ros2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roslibpy/ros2/__init__.py b/src/roslibpy/ros2/__init__.py index 2308388..02364ee 100644 --- a/src/roslibpy/ros2/__init__.py +++ b/src/roslibpy/ros2/__init__.py @@ -1,5 +1,5 @@ -from roslibpy import Time from roslibpy import Header as ROS1Header +from roslibpy import Time __all__ = [ "Header", From 480c89f326923fc144d67518a6c28a8c88370f87 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:23:16 +0200 Subject: [PATCH 13/16] No need for entry point? --- docker/ros1/Dockerfile | 4 +--- docker/ros1/ros_entrypoint.sh | 7 ------- docker/ros2/Dockerfile | 2 +- docker/ros2/ros_entrypoint.sh | 6 ------ 4 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 docker/ros1/ros_entrypoint.sh delete mode 100644 docker/ros2/ros_entrypoint.sh diff --git a/docker/ros1/Dockerfile b/docker/ros1/Dockerfile index 87ed789..0495d7e 100644 --- a/docker/ros1/Dockerfile +++ b/docker/ros1/Dockerfile @@ -13,11 +13,9 @@ RUN apt-get update && apt-get install -y \ # Clear apt-cache to reduce image size && rm -rf /var/lib/apt/lists/* -# Copy entrypoint -COPY ./ros_entrypoint.sh / +# Copy launch COPY ./integration-tests.launch / EXPOSE 9090 -ENTRYPOINT ["/ros_entrypoint.sh"] CMD ["bash"] diff --git a/docker/ros1/ros_entrypoint.sh b/docker/ros1/ros_entrypoint.sh deleted file mode 100644 index e393ff5..0000000 --- a/docker/ros1/ros_entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -e - -# Source ROS distro environment -source "/opt/ros/$ROS_DISTRO/setup.bash" - -exec "$@" diff --git a/docker/ros2/Dockerfile b/docker/ros2/Dockerfile index b4a0148..029eff5 100644 --- a/docker/ros2/Dockerfile +++ b/docker/ros2/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y \ # Clear apt-cache to reduce image size && rm -rf /var/lib/apt/lists/* -# Copy entrypoint +# Copy launch COPY ./integration-tests.launch / EXPOSE 9090 diff --git a/docker/ros2/ros_entrypoint.sh b/docker/ros2/ros_entrypoint.sh deleted file mode 100644 index 28bc65a..0000000 --- a/docker/ros2/ros_entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e - -# setup ros2 environment -source "/opt/ros/$ROS_DISTRO/setup.bash" -- -exec "$@" From f2d789eff9ac07df057b154ef744ac32ac91685a Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:29:12 +0200 Subject: [PATCH 14/16] Add back seq to ROS1 tests --- tests/ros1/test_core.py | 4 ++-- tests/ros1/test_topic.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ros1/test_core.py b/tests/ros1/test_core.py index cbe5f32..580414c 100644 --- a/tests/ros1/test_core.py +++ b/tests/ros1/test_core.py @@ -27,14 +27,14 @@ def test_is_zero(): def test_header_ctor_supports_time(): - header = Header(stamp=Time.from_sec(REF_FLOAT_SECS_TIME)) + header = Header(seq=1, stamp=Time.from_sec(REF_FLOAT_SECS_TIME)) assert header["stamp"]["secs"] == 1610122759 assert header["stamp"]["secs"] == header["stamp"].secs assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME def test_header_ctor_supports_dict(): - header = Header(stamp=dict(secs=1610122759, nsecs=677661895)) + header = Header(seq=1, stamp=dict(secs=1610122759, nsecs=677661895)) assert header["stamp"]["secs"] == 1610122759 assert header["stamp"]["secs"] == header["stamp"].secs assert header["stamp"].to_sec() == REF_FLOAT_SECS_TIME diff --git a/tests/ros1/test_topic.py b/tests/ros1/test_topic.py index 74e04c8..2178e61 100644 --- a/tests/ros1/test_topic.py +++ b/tests/ros1/test_topic.py @@ -69,7 +69,7 @@ def receive_message(message): def start_sending(): for _ in range(3): - msg = dict(header=Header(stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) + msg = dict(header=Header(seq=i, tamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) publisher.publish(Message(msg)) time.sleep(0.1) From ec5bc6e01ba45cfdb5b0b6634aa698904959ad14 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:45:55 +0200 Subject: [PATCH 15/16] ups --- tests/ros1/test_topic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ros1/test_topic.py b/tests/ros1/test_topic.py index 2178e61..e7ffc1b 100644 --- a/tests/ros1/test_topic.py +++ b/tests/ros1/test_topic.py @@ -68,7 +68,7 @@ def receive_message(message): context["wait"].set() def start_sending(): - for _ in range(3): + for i in range(3): msg = dict(header=Header(seq=i, tamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) publisher.publish(Message(msg)) time.sleep(0.1) From cb7e634d23752808771fb43d84f4e3540dc1525a Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Wed, 17 Apr 2024 11:49:03 +0200 Subject: [PATCH 16/16] omg --- tests/ros1/test_topic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ros1/test_topic.py b/tests/ros1/test_topic.py index e7ffc1b..97ba430 100644 --- a/tests/ros1/test_topic.py +++ b/tests/ros1/test_topic.py @@ -69,7 +69,7 @@ def receive_message(message): def start_sending(): for i in range(3): - msg = dict(header=Header(seq=i, tamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) + msg = dict(header=Header(seq=i, stamp=Time.now(), frame_id="base"), point=dict(x=0.0, y=1.0, z=2.0)) publisher.publish(Message(msg)) time.sleep(0.1)