├── rclpy ├── rclpy │ ├── py.typed │ ├── impl │ │ ├── service_introspection.pyi │ │ ├── __init__.py │ │ ├── implementation_singleton.pyi │ │ ├── implementation_singleton.py │ │ └── logging_severity.py │ ├── experimental │ │ ├── __init__.py │ │ └── events_executor.py │ ├── constants.py │ ├── clock_type.py │ ├── service_introspection.py │ ├── subscription_content_filter_options.py │ ├── action │ │ ├── __init__.py │ │ └── graph.py │ ├── topic_or_service_is_hidden.py │ ├── validate_parameter_name.py │ ├── validate_node_name.py │ ├── expand_topic_name.py │ ├── lifecycle │ │ ├── __init__.py │ │ └── publisher.py │ ├── validate_namespace.py │ ├── serialization.py │ ├── validate_full_topic_name.py │ ├── validate_topic_name.py │ ├── type_hash.py │ ├── logging.py │ ├── signals.py │ ├── guard_condition.py │ ├── wait_for_message.py │ └── logging_service.py ├── docs │ ├── source │ │ ├── api │ │ │ ├── clock.rst │ │ │ ├── node.rst │ │ │ ├── time.rst │ │ │ ├── timers.rst │ │ │ ├── context.rst │ │ │ ├── logging.rst │ │ │ ├── utilities.rst │ │ │ ├── qos.rst │ │ │ ├── init_shutdown.rst │ │ │ ├── services.rst │ │ │ ├── topics.rst │ │ │ ├── actions.rst │ │ │ ├── parameters.rst │ │ │ └── execution_and_callbacks.rst │ │ ├── examples.rst │ │ ├── about.rst │ │ ├── index.rst │ │ └── api.rst │ ├── Makefile │ └── make.bat ├── test │ ├── resources │ │ └── test_node │ │ │ ├── complicated_wildcards.yaml │ │ │ ├── test_parameters.yaml │ │ │ ├── params_by_order.yaml │ │ │ └── wildcards.yaml │ ├── test_destruction_order.py │ ├── __init__.py │ ├── test_validate_namespace.py │ ├── test_topic_or_service_is_hidden.py │ ├── test_type_support.py │ ├── test_validate_node_name.py │ ├── test_utilities.py │ ├── test_python_allocator.cpp │ ├── test_type_hash.py │ ├── test_context.py │ ├── test_validate_topic_name.py │ ├── test_validate_full_topic_name.py │ ├── test_messages.py │ ├── test_expand_topic_name.py │ ├── test_wait_for_message.py │ ├── test_type_description_service.py │ ├── test_serialization.py │ ├── test_guard_condition.py │ ├── test_logging_rosout.py │ ├── test_parameter_service.py │ └── test_init_shutdown.py ├── src │ └── rclpy │ │ ├── service_introspection.hpp │ │ ├── duration.hpp │ │ ├── lifecycle.hpp │ │ ├── logging_api.hpp │ │ ├── time_point.hpp │ │ ├── service_info.hpp │ │ ├── duration.cpp │ │ ├── service_introspection.cpp │ │ ├── service_info.cpp │ │ ├── time_point.cpp │ │ ├── events_executor │ │ ├── python_hasher.hpp │ │ ├── python_eq_handler.hpp │ │ ├── scoped_with.hpp │ │ ├── events_queue.cpp │ │ ├── delayed_event_thread.cpp │ │ ├── delayed_event_thread.hpp │ │ ├── events_queue.hpp │ │ ├── rcl_support.cpp │ │ ├── timers_manager.hpp │ │ └── rcl_support.hpp │ │ ├── logging.hpp │ │ ├── signal_handler.hpp │ │ ├── exceptions.cpp │ │ ├── destroyable.hpp │ │ ├── serialization.hpp │ │ ├── clock_event.hpp │ │ ├── guard_condition.hpp │ │ ├── type_description_service.hpp │ │ ├── python_allocator.hpp │ │ ├── exceptions.hpp │ │ ├── destroyable.cpp │ │ ├── logging.cpp │ │ ├── type_description_service.cpp │ │ ├── action_goal_handle.hpp │ │ ├── qos.hpp │ │ ├── guard_condition.cpp │ │ ├── context.hpp │ │ ├── event_handle.hpp │ │ ├── clock.hpp │ │ ├── clock_event.cpp │ │ ├── action_goal_handle.cpp │ │ └── serialization.cpp ├── rosdoc2.yaml └── package.xml ├── pytest.ini ├── README.md ├── .gitignore └── CONTRIBUTING.md /rclpy/rclpy/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | junit_family=xunit2 3 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/clock.rst: -------------------------------------------------------------------------------- 1 | Clock 2 | ===== 3 | 4 | .. automodule:: rclpy.clock 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/node.rst: -------------------------------------------------------------------------------- 1 | Node 2 | ==== 3 | 4 | .. automodule:: rclpy.node 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/time.rst: -------------------------------------------------------------------------------- 1 | Time 2 | ===== 3 | 4 | .. automodule:: rclpy.time 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/timers.rst: -------------------------------------------------------------------------------- 1 | Timer 2 | ===== 3 | 4 | .. automodule:: rclpy.timer 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/context.rst: -------------------------------------------------------------------------------- 1 | Context 2 | ======= 3 | 4 | .. automodule:: rclpy.context 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/logging.rst: -------------------------------------------------------------------------------- 1 | Logging 2 | ======= 3 | 4 | .. automodule:: rclpy.logging 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/utilities.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | .. automodule:: rclpy.utilities 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/qos.rst: -------------------------------------------------------------------------------- 1 | Quality of Service 2 | ================== 3 | 4 | .. automodule:: rclpy.qos 5 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/init_shutdown.rst: -------------------------------------------------------------------------------- 1 | Initialization, Shutdown, and Spinning 2 | ====================================== 3 | 4 | .. automodule:: rclpy 5 | -------------------------------------------------------------------------------- /rclpy/test/resources/test_node/complicated_wildcards.yaml: -------------------------------------------------------------------------------- 1 | /**/foo/*/bar: 2 | node2: 3 | ros__parameters: 4 | foo: "foo" 5 | bar: "bar" 6 | -------------------------------------------------------------------------------- /rclpy/docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | Examples for *rclpy* can be found on GitHub at `ros2/examples `__. 5 | -------------------------------------------------------------------------------- /rclpy/rclpy/impl/service_introspection.pyi: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class ServiceIntrospectionState(IntEnum): 5 | OFF = ... 6 | METADATA = ... 7 | CONTENTS = ... 8 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/services.rst: -------------------------------------------------------------------------------- 1 | Services 2 | ======== 3 | 4 | Client 5 | ------ 6 | 7 | .. automodule:: rclpy.client 8 | 9 | Service 10 | ------- 11 | 12 | .. automodule:: rclpy.service 13 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/topics.rst: -------------------------------------------------------------------------------- 1 | Topics 2 | ====== 3 | 4 | Publisher 5 | --------- 6 | 7 | .. automodule:: rclpy.publisher 8 | 9 | Subscription 10 | ------------ 11 | 12 | .. automodule:: rclpy.subscription 13 | -------------------------------------------------------------------------------- /rclpy/docs/source/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | The canonical Python API for `ROS 2 `_. 5 | 6 | *rclpy* is built on the common C-API provided by `rcl `_. 7 | 8 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/actions.rst: -------------------------------------------------------------------------------- 1 | Actions 2 | ======= 3 | 4 | Action Client 5 | ------------- 6 | 7 | .. automodule:: rclpy.action.client 8 | 9 | Action Server 10 | ------------- 11 | .. automodule:: rclpy.action.server 12 | -------------------------------------------------------------------------------- /rclpy/test/resources/test_node/test_parameters.yaml: -------------------------------------------------------------------------------- 1 | /**: 2 | ros__parameters: 3 | initial_fizz: -23 4 | initial_baz: 1.0 5 | initial_foobar: False 6 | /my_ns/my_node: 7 | ros__parameters: 8 | initial_fizz: param_file_override 9 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/parameters.rst: -------------------------------------------------------------------------------- 1 | Parameters 2 | ========== 3 | 4 | Parameter 5 | --------- 6 | 7 | .. automodule:: rclpy.parameter 8 | 9 | Parameter Service 10 | ----------------- 11 | 12 | .. automodule:: rclpy.parameter_service 13 | 14 | Parameter Client 15 | ----------------- 16 | 17 | .. automodule:: rclpy.parameter_client 18 | -------------------------------------------------------------------------------- /rclpy/test/resources/test_node/params_by_order.yaml: -------------------------------------------------------------------------------- 1 | /**: 2 | node2: 3 | ros__parameters: 4 | a_value: "first" 5 | foo: "foo" 6 | 7 | /ns: 8 | node2: 9 | ros__parameters: 10 | a_value: "second" 11 | bar: "bar" 12 | 13 | /*: 14 | node2: 15 | ros__parameters: 16 | a_value: "last_one_win" 17 | -------------------------------------------------------------------------------- /rclpy/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | rclpy 2 | ===== 3 | 4 | rclpy provides the canonical Python API for interacting with ROS 2. 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | about 10 | examples 11 | api 12 | modules 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /rclpy/docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | api/clock 9 | api/init_shutdown 10 | api/node 11 | api/topics 12 | api/services 13 | api/actions 14 | api/time 15 | api/timers 16 | api/parameters 17 | api/logging 18 | api/context 19 | api/execution_and_callbacks 20 | api/utilities 21 | api/qos 22 | 23 | .. TODO(jacobperron): Document remaining API 24 | api/time 25 | -------------------------------------------------------------------------------- /rclpy/docs/source/api/execution_and_callbacks.rst: -------------------------------------------------------------------------------- 1 | Execution and Callbacks 2 | ======================= 3 | 4 | There are two components that control the execution of callbacks: **executors** and **callback groups**. 5 | 6 | Executors are responsible for the actual execution of callbacks and should extend the :class:`.Executor` class. 7 | 8 | Callback groups are used to enforce concurrency rules for callbacks and should extend the :class:`.CallbackGroup` class. 9 | 10 | Executors 11 | --------- 12 | 13 | .. automodule:: rclpy.executors 14 | 15 | 16 | Callback Groups 17 | --------------- 18 | .. automodule:: rclpy.callback_groups 19 | -------------------------------------------------------------------------------- /rclpy/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /rclpy/rclpy/impl/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /rclpy/rclpy/experimental/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024-2025 Brad Martin 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .events_executor import EventsExecutor as EventsExecutor # noqa: F401 16 | -------------------------------------------------------------------------------- /rclpy/rclpy/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Final 15 | 16 | 17 | S_TO_NS: Final = 1000 * 1000 * 1000 18 | -------------------------------------------------------------------------------- /rclpy/rclpy/impl/implementation_singleton.pyi: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from rclpy.impl import _rclpy_pybind11 17 | 18 | rclpy_implementation = _rclpy_pybind11 19 | -------------------------------------------------------------------------------- /rclpy/rclpy/clock_type.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 16 | from typing_extensions import TypeAlias 17 | 18 | ClockType: TypeAlias = _rclpy.ClockType 19 | -------------------------------------------------------------------------------- /rclpy/rclpy/service_introspection.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 16 | from typing_extensions import TypeAlias 17 | 18 | ServiceIntrospectionState: TypeAlias = _rclpy.service_introspection.ServiceIntrospectionState 19 | -------------------------------------------------------------------------------- /rclpy/rclpy/subscription_content_filter_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Sony Group Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import NamedTuple 16 | 17 | 18 | class ContentFilterOptions(NamedTuple): 19 | """Options to configure content filtered topic in the subscription.""" 20 | 21 | filter_expression: str 22 | expression_parameters: list[str] 23 | -------------------------------------------------------------------------------- /rclpy/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rclpy 2 | ROS Client Library for the Python language. 3 | 4 | 5 | ## Building documentation 6 | 7 | Documentation can be built for `rclpy` using [Sphinx](http://www.sphinx-doc.org/en/master/), or accessed [online](https://docs.ros.org/en/rolling/p/rclpy/). 8 | 9 | For building documentation, you need an installation of ROS 2. 10 | 11 | #### Install dependencies 12 | 13 | sudo apt install \ 14 | python3-sphinx \ 15 | python3-sphinx-autodoc-typehints \ 16 | python3-sphinx-rtd-theme 17 | 18 | #### Build 19 | 20 | Source your ROS 2 installation, for example: 21 | 22 | . /opt/ros/rolling/setup.bash 23 | 24 | Build code: 25 | 26 | mkdir -p rclpy_ws/src 27 | cd rclpy_ws/src 28 | git clone https://github.com/ros2/rclpy.git 29 | cd .. 30 | colcon build --symlink-install 31 | 32 | Source workspace and build docs: 33 | 34 | source install/setup.bash 35 | cd src/rclpy/rclpy/docs 36 | make html 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | .pytest_cache 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Any contribution that you make to this repository will 2 | be under the Apache 2 License, as dictated by that 3 | [license](http://www.apache.org/licenses/LICENSE-2.0.html): 4 | 5 | ~~~ 6 | 5. Submission of Contributions. Unless You explicitly state otherwise, 7 | any Contribution intentionally submitted for inclusion in the Work 8 | by You to the Licensor shall be under the terms and conditions of 9 | this License, without any additional terms or conditions. 10 | Notwithstanding the above, nothing herein shall supersede or modify 11 | the terms of any separate license agreement you may have executed 12 | with Licensor regarding such Contributions. 13 | ~~~ 14 | 15 | Contributors must sign-off each commit by adding a `Signed-off-by: ...` 16 | line to commit messages to certify that they have the right to submit 17 | the code they are contributing to the project according to the 18 | [Developer Certificate of Origin (DCO)](https://developercertificate.org/). 19 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/service_introspection.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__SERVICE_INTROSPECTION_HPP_ 16 | #define RCLPY__SERVICE_INTROSPECTION_HPP_ 17 | 18 | #include "pybind11/pybind11.h" 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | 25 | void 26 | define_service_introspection(py::module module); 27 | 28 | } // namespace rclpy 29 | 30 | #endif // RCLPY__SERVICE_INTROSPECTION_HPP_ 31 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/duration.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__DURATION_HPP_ 16 | #define RCLPY__DURATION_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// Define a pybind11 wrapper for an rcl_duration_t 25 | /** 26 | * \param[in] module a pybind11 module to add the definition to 27 | */ 28 | void define_duration(py::object module); 29 | } // namespace rclpy 30 | 31 | #endif // RCLPY__DURATION_HPP_ 32 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/lifecycle.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__LIFECYCLE_HPP_ 16 | #define RCLPY__LIFECYCLE_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// Define methods on a module for the lifecycle API 25 | /** 26 | * \param[in] module a pybind11 module to add the definition to 27 | */ 28 | void 29 | define_lifecycle_api(py::module module); 30 | } // namespace rclpy 31 | #endif // RCLPY__LIFECYCLE_HPP_ 32 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/logging_api.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__LOGGING_API_HPP_ 16 | #define RCLPY__LOGGING_API_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// Define methods on a module for the logging API 25 | /** 26 | * \param[in] module a pybind11 module to add the definition to 27 | */ 28 | void 29 | define_logging_api(py::module module); 30 | } // namespace rclpy 31 | #endif // RCLPY__LOGGING_API_HPP_ 32 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/time_point.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__TIME_POINT_HPP_ 16 | #define RCLPY__TIME_POINT_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// Define a pybind11 wrapper for an rcl_time_point_t 25 | /** 26 | * \param[in] module a pybind11 module to add the definition to 27 | */ 28 | void define_time_point(py::object module); 29 | } // namespace rclpy 30 | 31 | #endif // RCLPY__TIME_POINT_HPP_ 32 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/service_info.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__SERVICE_INFO_HPP_ 16 | #define RCLPY__SERVICE_INFO_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// Define a pybind11 wrapper for an rmw_service_info_t and rmw_request_id_t 25 | /** 26 | * \param[in] module a pybind11 module to add the definition to 27 | */ 28 | void define_service_info(py::object module); 29 | } // namespace rclpy 30 | 31 | #endif // RCLPY__SERVICE_INFO_HPP_ 32 | -------------------------------------------------------------------------------- /rclpy/rclpy/impl/implementation_singleton.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Provide singleton access to the rclpy C modules. 17 | 18 | For example, you might use it like this: 19 | 20 | .. code:: 21 | 22 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 23 | 24 | _rclpy.rclpy_init() 25 | while _rclpy.rclpy_ok(): 26 | # ... 27 | """ 28 | 29 | from rpyutils import import_c_library 30 | 31 | package = 'rclpy' 32 | 33 | rclpy_implementation = import_c_library('._rclpy_pybind11', package) 34 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/duration.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "duration.hpp" 20 | 21 | namespace rclpy 22 | { 23 | rcl_duration_t 24 | create_duration(int64_t nanoseconds) 25 | { 26 | rcl_duration_t duration; 27 | duration.nanoseconds = nanoseconds; 28 | return duration; 29 | } 30 | void 31 | define_duration(py::object module) 32 | { 33 | py::class_(module, "rcl_duration_t") 34 | .def(py::init<>(&create_duration)) 35 | .def_readonly("nanoseconds", &rcl_duration_t::nanoseconds); 36 | } 37 | } // namespace rclpy 38 | -------------------------------------------------------------------------------- /rclpy/test/resources/test_node/wildcards.yaml: -------------------------------------------------------------------------------- 1 | /**: 2 | ros__parameters: 3 | full_wild: "full_wild" 4 | 5 | /**: 6 | node2: 7 | ros__parameters: 8 | namespace_wild: "namespace_wild" 9 | 10 | /**/node2: 11 | ros__parameters: 12 | namespace_wild_another: "namespace_wild_another" 13 | 14 | /*: 15 | node2: 16 | ros__parameters: 17 | namespace_wild_one_star: "namespace_wild_one_star" 18 | 19 | ns: 20 | "*": 21 | ros__parameters: 22 | node_wild_in_ns: "node_wild_in_ns" 23 | 24 | /ns/*: 25 | ros__parameters: 26 | node_wild_in_ns_another: "node_wild_in_ns_another" 27 | 28 | ns: 29 | node2: 30 | ros__parameters: 31 | explicit_in_ns: "explicit_in_ns" 32 | 33 | "*": 34 | ros__parameters: 35 | node_wild_no_ns: "node_wild_no_ns" 36 | 37 | node2: 38 | ros__parameters: 39 | explicit_no_ns: "explicit_no_ns" 40 | 41 | ns: 42 | nodeX: 43 | ros__parameters: 44 | should_not_appear: "incorrect_node_name" 45 | 46 | /**/nodeX: 47 | ros__parameters: 48 | should_not_appear: "incorrect_node_name" 49 | 50 | nsX: 51 | node2: 52 | ros__parameters: 53 | should_not_appear: "incorrect_namespace" 54 | 55 | /nsX/*: 56 | ros__parameters: 57 | should_not_appear: "incorrect_namespace" 58 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/service_introspection.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "service_introspection.hpp" 16 | #include "rcl/service_introspection.h" 17 | 18 | namespace rclpy 19 | { 20 | 21 | void 22 | define_service_introspection(py::module module) 23 | { 24 | py::module m2 = module.def_submodule( 25 | "service_introspection", 26 | "utilities for introspecting services"); 27 | 28 | py::enum_(m2, "ServiceIntrospectionState") 29 | .value("OFF", RCL_SERVICE_INTROSPECTION_OFF) 30 | .value("METADATA", RCL_SERVICE_INTROSPECTION_METADATA) 31 | .value("CONTENTS", RCL_SERVICE_INTROSPECTION_CONTENTS); 32 | } 33 | } // namespace rclpy 34 | -------------------------------------------------------------------------------- /rclpy/rclpy/action/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .client import ActionClient as ActionClient # noqa: F401 16 | from .graph import ( # noqa: F401 17 | get_action_client_names_and_types_by_node as get_action_client_names_and_types_by_node 18 | ) 19 | from .graph import get_action_names_and_types as get_action_names_and_types # noqa: F401 20 | from .graph import ( # noqa: F401 21 | get_action_server_names_and_types_by_node as get_action_server_names_and_types_by_node 22 | ) 23 | from .server import ActionServer as ActionServer # noqa: F401 24 | from .server import CancelResponse as CancelResponse # noqa: F401 25 | from .server import GoalResponse as GoalResponse # noqa: F401 26 | -------------------------------------------------------------------------------- /rclpy/rclpy/topic_or_service_is_hidden.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | HIDDEN_TOPIC_PREFIX = '_' 16 | 17 | 18 | def topic_or_service_is_hidden(name: str) -> bool: 19 | """ 20 | Return True if a given topic or service name is hidden, otherwise False. 21 | 22 | A topic or service name is considered hidden if any of the name tokens 23 | begins with an underscore (``_``). 24 | See: 25 | 26 | http://design.ros2.org/articles/topic_and_service_names.html#hidden-topic-or-service-names 27 | 28 | :param name: topic or service name to test 29 | :returns: True if name is hidden, otherwise False 30 | """ 31 | return any(token for token in name.split('/') if token.startswith(HIDDEN_TOPIC_PREFIX)) 32 | -------------------------------------------------------------------------------- /rclpy/rclpy/impl/logging_severity.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from enum import IntEnum 17 | 18 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 19 | 20 | 21 | class LoggingSeverity(IntEnum): 22 | """ 23 | Enum for logging severity levels. 24 | 25 | This enum must match the one defined in rcutils/logging.h 26 | """ 27 | 28 | UNSET = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_UNSET 29 | DEBUG = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_DEBUG 30 | INFO = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_INFO 31 | WARN = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_WARN 32 | ERROR = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_ERROR 33 | FATAL = _rclpy.RCUTILS_LOG_SEVERITY.RCUTILS_LOG_SEVERITY_FATAL 34 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/service_info.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "service_info.hpp" 20 | 21 | namespace rclpy 22 | { 23 | 24 | void 25 | define_service_info(py::object module) 26 | { 27 | py::class_(module, "rmw_service_info_t") 28 | .def_readonly("source_timestamp", &rmw_service_info_t::source_timestamp) 29 | .def_readonly("received_timestamp", &rmw_service_info_t::received_timestamp) 30 | .def_readonly("request_id", &rmw_service_info_t::request_id); 31 | 32 | py::class_(module, "rmw_request_id_t") 33 | .def_readonly("sequence_number", &rmw_request_id_t::sequence_number); 34 | // "writer_guid" is not included because it's not used by rclpy 35 | } 36 | } // namespace rclpy 37 | -------------------------------------------------------------------------------- /rclpy/rclpy/validate_parameter_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal 16 | 17 | from rclpy.exceptions import InvalidParameterException 18 | 19 | 20 | def validate_parameter_name(name: str) -> Literal[True]: 21 | """ 22 | Validate a given parameter name, and raise an exception if invalid. 23 | 24 | The name does not have to be fully-qualified and is not expanded. 25 | 26 | If the name is invalid then rclpy.exceptions.InvalidParameterException 27 | will be raised. 28 | 29 | :param name: parameter name to be validated. 30 | :raises: InvalidParameterException: when the name is invalid. 31 | """ 32 | # TODO(jubeira): add parameter name check to be implemented at RCL level. 33 | if not name: 34 | raise InvalidParameterException(name) 35 | 36 | return True 37 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/time_point.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "time_point.hpp" 20 | 21 | namespace rclpy 22 | { 23 | /// Create a time point 24 | rcl_time_point_t 25 | create_time_point(int64_t nanoseconds, int clock_type) 26 | { 27 | rcl_time_point_t time_point; 28 | time_point.nanoseconds = nanoseconds; 29 | time_point.clock_type = rcl_clock_type_t(clock_type); 30 | return time_point; 31 | } 32 | 33 | void 34 | define_time_point(py::object module) 35 | { 36 | py::class_(module, "rcl_time_point_t") 37 | .def(py::init<>(&create_time_point)) 38 | .def_readonly("nanoseconds", &rcl_time_point_t::nanoseconds) 39 | .def_readonly("clock_type", &rcl_time_point_t::clock_type); 40 | } 41 | } // namespace rclpy 42 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/python_hasher.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 Brad Martin 2 | // Copyright 2024 Merlin Labs, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef RCLPY__EVENTS_EXECUTOR__PYTHON_HASHER_HPP_ 17 | #define RCLPY__EVENTS_EXECUTOR__PYTHON_HASHER_HPP_ 18 | 19 | #include 20 | 21 | namespace rclpy 22 | { 23 | namespace events_executor 24 | { 25 | /// This is intended to be used as the Hash template arg to STL containers using a 26 | /// pybind11::handle as a Key. This is the same hash that a native Python dict or set 27 | /// would use given the same key. 28 | struct PythonHasher 29 | { 30 | inline auto operator()(const pybind11::handle & handle) const 31 | { 32 | return pybind11::hash(handle); 33 | } 34 | }; 35 | } // namespace events_executor 36 | } // namespace rclpy 37 | 38 | #endif // RCLPY__EVENTS_EXECUTOR__PYTHON_HASHER_HPP_ 39 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/python_eq_handler.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__EVENTS_EXECUTOR__PYTHON_EQ_HANDLER_HPP_ 16 | #define RCLPY__EVENTS_EXECUTOR__PYTHON_EQ_HANDLER_HPP_ 17 | 18 | #include 19 | 20 | namespace rclpy 21 | { 22 | namespace events_executor 23 | { 24 | /// This is a workaround to the deprecation of `operator==` in Pybind11 >=2.2 25 | /// See https://pybind11.readthedocs.io/en/stable/upgrade.html#deprecation-of-some-py-object-apis 26 | /// It's intended to be replaced with the 27 | struct PythonEqHandler 28 | { 29 | inline auto operator()(const pybind11::handle & x, const pybind11::handle & y) const 30 | { 31 | return x.is(y); 32 | } 33 | }; 34 | } // namespace events_executor 35 | } // namespace rclpy 36 | 37 | #endif // RCLPY__EVENTS_EXECUTOR__PYTHON_EQ_HANDLER_HPP_ 38 | -------------------------------------------------------------------------------- /rclpy/test/test_destruction_order.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import rclpy 16 | from rclpy.node import Node 17 | 18 | 19 | def test_destruction_order() -> None: 20 | context = rclpy.context.Context() 21 | rclpy.init(context=context) 22 | node1 = Node('test_destruction_order_node_1', context=context) 23 | node2 = Node('test_destruction_order_node_2', context=context) 24 | node3 = Node('test_destruction_order_node_3', context=context) 25 | 26 | node1.cyclic_ref = node1 # type: ignore[attr-defined] 27 | node2.cyclic_ref = node2 # type: ignore[attr-defined] 28 | node1.node3 = node3 # type: ignore[attr-defined] 29 | node2.node3 = node3 # type: ignore[attr-defined] 30 | 31 | node1.handle.context_handle = context.handle # type: ignore[attr-defined] 32 | node2.handle.context_handle = context.handle # type: ignore[attr-defined] 33 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/scoped_with.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 Brad Martin 2 | // Copyright 2024 Merlin Labs, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef RCLPY__EVENTS_EXECUTOR__SCOPED_WITH_HPP_ 17 | #define RCLPY__EVENTS_EXECUTOR__SCOPED_WITH_HPP_ 18 | 19 | #include 20 | 21 | namespace rclpy 22 | { 23 | namespace events_executor 24 | { 25 | 26 | /// Enters a python context manager for the scope of this object instance. 27 | class ScopedWith 28 | { 29 | public: 30 | explicit ScopedWith(pybind11::handle object) 31 | : object_(pybind11::cast(object)) 32 | { 33 | object_.attr("__enter__")(); 34 | } 35 | 36 | ~ScopedWith() {object_.attr("__exit__")(pybind11::none(), pybind11::none(), pybind11::none());} 37 | 38 | private: 39 | pybind11::object object_; 40 | }; 41 | 42 | } // namespace events_executor 43 | } // namespace rclpy 44 | 45 | #endif // RCLPY__EVENTS_EXECUTOR__SCOPED_WITH_HPP_ 46 | -------------------------------------------------------------------------------- /rclpy/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import importlib 16 | import sys 17 | from types import ModuleType 18 | from typing import Optional 19 | 20 | from rpyutils import add_dll_directories_from_env 21 | 22 | assert 'rclpy' not in sys.modules, 'rclpy should not have been imported before running tests' 23 | 24 | # this will make the extensions load from the build folder 25 | import rpyutils # noqa 26 | import test_rclpy # noqa 27 | 28 | 29 | def _custom_import(name: str, package: Optional[str] = None) -> ModuleType: 30 | # Since Python 3.8, on Windows we should ensure DLL directories are explicitly added 31 | # to the search path. 32 | # See https://docs.python.org/3/whatsnew/3.8.html#bpo-36085-whatsnew 33 | with add_dll_directories_from_env('PATH'): 34 | return importlib.import_module(name, package='test_rclpy') 35 | 36 | 37 | rpyutils.import_c_library = _custom_import 38 | -------------------------------------------------------------------------------- /rclpy/rclpy/validate_node_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal 16 | 17 | from rclpy.exceptions import InvalidNodeNameException 18 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 19 | 20 | 21 | def validate_node_name(node_name: str) -> Literal[True]: 22 | """ 23 | Validate a given node_name, and raise an exception if it is invalid. 24 | 25 | If the node_name is invalid then rclpy.exceptions.InvalidNodeNameException 26 | will be raised. 27 | 28 | :param node_name: node_name to be validated 29 | :returns: True when it is valid 30 | :raises: InvalidNodeNameException: when the node_name is invalid 31 | """ 32 | result = _rclpy.rclpy_get_validation_error_for_node_name(node_name) 33 | if result is None: 34 | return True 35 | error_msg, invalid_index = result 36 | raise InvalidNodeNameException(node_name, error_msg, invalid_index) 37 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/logging.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__LOGGING_HPP_ 16 | #define RCLPY__LOGGING_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include "context.hpp" 23 | 24 | namespace py = pybind11; 25 | 26 | namespace rclpy 27 | { 28 | /// Acquire the global logging mutex when constructed, and release it when destructed 29 | class LoggingGuard 30 | { 31 | public: 32 | LoggingGuard(); 33 | 34 | private: 35 | static std::recursive_mutex logging_mutex_; 36 | std::lock_guard guard_; 37 | }; 38 | 39 | /// Initialize rcl logging 40 | /** 41 | * Raises RuntimeError if rcl logging could not be initialized 42 | * \param[in] _context A context instance to use to retrieve global CLI arguments. 43 | */ 44 | void 45 | logging_configure(Context & _context); 46 | 47 | /// Finalize rcl logging 48 | void 49 | logging_fini(void); 50 | } // namespace rclpy 51 | 52 | #endif // RCLPY__LOGGING_HPP_ 53 | -------------------------------------------------------------------------------- /rclpy/test/test_validate_namespace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.exceptions import InvalidNamespaceException 18 | from rclpy.validate_namespace import validate_namespace 19 | 20 | 21 | class TestValidateNamespace(unittest.TestCase): 22 | 23 | def test_validate_namespace(self) -> None: 24 | tests = [ 25 | '/my_ns', 26 | '/', 27 | ] 28 | for topic in tests: 29 | # Will raise if invalid 30 | validate_namespace(topic) 31 | 32 | def test_validate_namespace_failures(self) -> None: 33 | # namespace must not be empty 34 | with self.assertRaisesRegex(InvalidNamespaceException, 'empty'): 35 | validate_namespace('') 36 | # namespace must start with / 37 | with self.assertRaisesRegex(InvalidNamespaceException, 'must be absolute'): 38 | validate_namespace('invalid_namespace') 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /rclpy/rclpy/expand_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 16 | 17 | 18 | def expand_topic_name(topic_name: str, node_name: str, node_namespace: str) -> str: 19 | """ 20 | Expand a given topic name using given node name and namespace as well. 21 | 22 | Note that this function can succeed but the expanded topic name may still 23 | be invalid. 24 | The :py:func:validate_full_topic_name(): should be used on the expanded 25 | topic name to ensure it is valid after expansion. 26 | 27 | :param topic_name: topic name to be expanded 28 | :param node_name: name of the node that this topic is associated with 29 | :param namespace: namespace that the topic is within 30 | :returns: expanded topic name which is fully qualified 31 | :raises: ValueError if the topic name, node name or namespace are invalid 32 | """ 33 | return _rclpy.rclpy_expand_topic_name(topic_name, node_name, node_namespace) 34 | -------------------------------------------------------------------------------- /rclpy/rclpy/lifecycle/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from .managed_entity import ManagedEntity 16 | from .managed_entity import SimpleManagedEntity 17 | from .node import LifecycleNode 18 | from .node import LifecycleNodeMixin 19 | from .node import LifecycleState 20 | from .publisher import LifecyclePublisher 21 | 22 | from ..impl.implementation_singleton import rclpy_implementation as _rclpy 23 | 24 | # reexport LifecycleNode as Node, so it's possible to write: 25 | # from rclpy.lifecycle import Node 26 | # Do not include that in __all__ to avoid mixing it up with rclpy.node.Node. 27 | Node = LifecycleNode 28 | # same idea here 29 | NodeMixin = LifecycleNodeMixin 30 | State = LifecycleState 31 | Publisher = LifecyclePublisher 32 | 33 | # enum defined in pybind11 plugin 34 | TransitionCallbackReturn = _rclpy.TransitionCallbackReturnType 35 | 36 | 37 | __all__ = [ 38 | 'LifecycleNodeMixin', 39 | 'LifecycleNode', 40 | 'LifecycleState', 41 | 'LifecyclePublisher', 42 | 'ManagedEntity', 43 | 'SimpleManagedEntity', 44 | ] 45 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/signal_handler.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__SIGNAL_HANDLER_HPP_ 16 | #define RCLPY__SIGNAL_HANDLER_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | namespace py = pybind11; 24 | 25 | namespace rclpy 26 | { 27 | 28 | /// Register a C++ callback to be invoked when a signal is caught. These callbacks will be invoked 29 | /// on an arbitrary thread. The callback will be automatically unregistered if the object goes out 30 | /// of scope. 31 | class ScopedSignalCallback { 32 | public: 33 | explicit ScopedSignalCallback(std::function); 34 | ~ScopedSignalCallback(); 35 | 36 | private: 37 | class Impl; 38 | std::shared_ptr impl_; 39 | }; 40 | 41 | /// Define methods on a module for working with signal handlers 42 | /** 43 | * \param[in] module a pybind11 module to add the definition to 44 | */ 45 | void 46 | define_signal_handler_api(py::module module); 47 | 48 | } // namespace rclpy 49 | #endif // RCLPY__SIGNAL_HANDLER_HPP_ 50 | -------------------------------------------------------------------------------- /rclpy/rclpy/validate_namespace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal 16 | 17 | from rclpy.exceptions import InvalidNamespaceException 18 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 19 | 20 | 21 | def validate_namespace(namespace: str) -> Literal[True]: 22 | """ 23 | Validate a given namespace, and raise an exception if it is invalid. 24 | 25 | Unlike the node constructor, which allows namespaces without a leading '/' 26 | and empty namespaces "", this function requires that the namespace be 27 | absolute and non-empty. 28 | 29 | If the namespace is invalid then rclpy.exceptions.InvalidNamespaceException 30 | will be raised. 31 | 32 | :param namespace: namespace to be validated 33 | :returns: True when it is valid 34 | :raises: InvalidNamespaceException: when the namespace is invalid 35 | """ 36 | result = _rclpy.rclpy_get_validation_error_for_namespace(namespace) 37 | if result is None: 38 | return True 39 | error_msg, invalid_index = result 40 | raise InvalidNamespaceException(namespace, error_msg, invalid_index) 41 | -------------------------------------------------------------------------------- /rclpy/rclpy/serialization.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Type 15 | 16 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 17 | from rclpy.type_support import check_for_type_support, Msg, MsgT 18 | 19 | 20 | def serialize_message(message: Msg) -> bytes: 21 | """ 22 | Serialize a ROS message. 23 | 24 | :param message: The ROS message to serialize. 25 | :return: The serialized bytes. 26 | """ 27 | message_type = type(message) 28 | # this line imports the typesupport for the message module if not already done 29 | check_for_type_support(message_type) 30 | return _rclpy.rclpy_serialize(message, message_type) 31 | 32 | 33 | def deserialize_message(serialized_message: bytes, message_type: Type[MsgT]) -> MsgT: 34 | """ 35 | Deserialize a ROS message. 36 | 37 | :param serialized_message: The ROS message to deserialize. 38 | :param message_type: The type of the serialized ROS message. 39 | :return: The deserialized ROS message. 40 | """ 41 | # this line imports the typesupport for the message module if not already done 42 | check_for_type_support(message_type) 43 | return _rclpy.rclpy_deserialize(serialized_message, message_type) 44 | -------------------------------------------------------------------------------- /rclpy/rclpy/validate_full_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal 16 | 17 | from rclpy.exceptions import InvalidServiceNameException 18 | from rclpy.exceptions import InvalidTopicNameException 19 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 20 | 21 | 22 | def validate_full_topic_name(name: str, *, is_service: bool = False) -> Literal[True]: 23 | """ 24 | Validate a given topic or service name, and raise an exception if invalid. 25 | 26 | The name must be fully-qualified and already expanded. 27 | 28 | If the name is invalid then rclpy.exceptions.InvalidTopicNameException 29 | will be raised. 30 | 31 | :param name: topic or service name to be validated 32 | :param is_service: if true, InvalidServiceNameException is raised 33 | :returns: True when it is valid 34 | :raises: InvalidTopicNameException: when the name is invalid 35 | """ 36 | result = _rclpy.rclpy_get_validation_error_for_full_topic_name(name) 37 | if result is None: 38 | return True 39 | error_msg, invalid_index = result 40 | if is_service: 41 | raise InvalidServiceNameException(name, error_msg, invalid_index) 42 | else: 43 | raise InvalidTopicNameException(name, error_msg, invalid_index) 44 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/exceptions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "exceptions.hpp" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | namespace rclpy 25 | { 26 | std::string append_rcutils_error(std::string prepend) 27 | { 28 | prepend += ": "; 29 | prepend += rcutils_get_error_string().str; 30 | rcutils_reset_error(); 31 | return prepend; 32 | } 33 | 34 | std::string append_rcl_error(std::string prepend) 35 | { 36 | prepend += ": "; 37 | prepend += rcl_get_error_string().str; 38 | rcl_reset_error(); 39 | return prepend; 40 | } 41 | 42 | std::string append_rmw_error(std::string prepend) 43 | { 44 | prepend += ": "; 45 | prepend += rmw_get_error_string().str; 46 | rmw_reset_error(); 47 | return prepend; 48 | } 49 | 50 | RCUtilsError::RCUtilsError(const std::string & error_text) 51 | : std::runtime_error(append_rcl_error(error_text)) 52 | { 53 | } 54 | 55 | RCLError::RCLError(const std::string & error_text) 56 | : std::runtime_error(append_rcl_error(error_text)) 57 | { 58 | } 59 | 60 | RMWError::RMWError(const std::string & error_text) 61 | : std::runtime_error(append_rmw_error(error_text)) 62 | { 63 | } 64 | } // namespace rclpy 65 | -------------------------------------------------------------------------------- /rclpy/test/test_topic_or_service_is_hidden.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.topic_or_service_is_hidden import topic_or_service_is_hidden 18 | 19 | 20 | class TestTopicOrServiceIsHidden(unittest.TestCase): 21 | 22 | def test_topic_or_service_is_hidden(self) -> None: 23 | tests = [ 24 | ('/chatter', False), 25 | ('chatter', False), 26 | ('/_chatter', True), 27 | ('_chatter', True), 28 | ('/more/complex/chatter', False), 29 | ('/_more/complex/chatter', True), 30 | ('/more/_complex/chatter', True), 31 | ('/more/complex/_chatter', True), 32 | ('/more/complex_/chatter', False), 33 | ('/more/complex/_/chatter', True), 34 | ('_/chatter', True), 35 | ('/chatter_', False), 36 | ('/more_/complex/chatter', False), 37 | ('/more/complex_/chatter', False), 38 | ('/more/complex/chatter_', False), 39 | ('/_more/_complex/_chatter', True), 40 | ('', False), 41 | ('_', True), 42 | ] 43 | for topic, expected in tests: 44 | self.assertEqual(expected, topic_or_service_is_hidden(topic)) 45 | 46 | 47 | if __name__ == '__main__': 48 | unittest.main() 49 | -------------------------------------------------------------------------------- /rclpy/rclpy/validate_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal 16 | 17 | from rclpy.exceptions import InvalidServiceNameException 18 | from rclpy.exceptions import InvalidTopicNameException 19 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 20 | 21 | TOPIC_SEPARATOR_STRING = '/' 22 | 23 | 24 | def validate_topic_name(name: str, *, is_service: bool = False) -> Literal[True]: 25 | """ 26 | Validate a given topic or service name, and raise an exception if invalid. 27 | 28 | The name does not have to be fully-qualified and is not expanded. 29 | 30 | If the name is invalid then rclpy.exceptions.InvalidTopicNameException 31 | will be raised. 32 | 33 | :param name: topic or service name to be validated 34 | :param is_service: if true, InvalidServiceNameException is raised 35 | :returns: True when it is valid 36 | :raises: InvalidTopicNameException: when the name is invalid 37 | """ 38 | result = _rclpy.rclpy_get_validation_error_for_topic_name(name) 39 | if result is None: 40 | return True 41 | error_msg, invalid_index = result 42 | if is_service: 43 | raise InvalidServiceNameException(name, error_msg, invalid_index) 44 | else: 45 | raise InvalidTopicNameException(name, error_msg, invalid_index) 46 | -------------------------------------------------------------------------------- /rclpy/test/test_type_support.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from rclpy import type_support 18 | from rclpy.exceptions import NoTypeSupportImportedException 19 | 20 | from test_msgs.msg import Strings 21 | from test_msgs.srv import Empty 22 | 23 | 24 | class MockTypeMetaclass(type): 25 | _TYPE_SUPPORT = None 26 | 27 | @classmethod 28 | def __import_type_support__(cls) -> None: 29 | pass 30 | 31 | 32 | class MockType(metaclass=MockTypeMetaclass): 33 | pass 34 | 35 | 36 | def test_check_for_type_support() -> None: 37 | type_support.check_for_type_support(Strings) 38 | type_support.check_for_type_support(Empty) 39 | with pytest.raises(AttributeError): 40 | type_support.check_for_type_support(object()) # type: ignore[arg-type] 41 | with pytest.raises(NoTypeSupportImportedException): 42 | type_support.check_for_type_support(MockType) 43 | 44 | 45 | def test_check_valid_msg_type() -> None: 46 | type_support.check_is_valid_msg_type(Strings) 47 | with pytest.raises(RuntimeError): 48 | type_support.check_is_valid_msg_type(Empty) 49 | 50 | 51 | def test_check_valid_srv_type() -> None: 52 | type_support.check_is_valid_srv_type(Empty) 53 | with pytest.raises(RuntimeError): 54 | type_support.check_is_valid_srv_type(Strings) 55 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/destroyable.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__DESTROYABLE_HPP_ 16 | #define RCLPY__DESTROYABLE_HPP_ 17 | 18 | #include 19 | 20 | namespace py = pybind11; 21 | 22 | namespace rclpy 23 | { 24 | /// This class blocks destruction when in use 25 | class Destroyable 26 | { 27 | public: 28 | /// Default constructor 29 | Destroyable() = default; 30 | 31 | /// Copy constructor 32 | Destroyable(const Destroyable & other); 33 | 34 | /// Context manager __enter__ - block destruction 35 | void 36 | enter(); 37 | 38 | /// Context manager __exit__ - unblock destruction 39 | void 40 | exit(py::object pytype, py::object pyvalue, py::object pytraceback); 41 | 42 | /// Signal that the object should be destroyed as soon as it's not in use 43 | void 44 | destroy_when_not_in_use(); 45 | 46 | /// Override this to destroy an object 47 | virtual 48 | void 49 | destroy(); 50 | 51 | virtual 52 | ~Destroyable() = default; 53 | 54 | private: 55 | size_t use_count = 0u; 56 | bool please_destroy_ = false; 57 | }; 58 | 59 | /// Define a pybind11 wrapper for an rclpy::Destroyable 60 | /** 61 | * \param[in] module a pybind11 module to add the definition to 62 | */ 63 | void define_destroyable(py::object module); 64 | } // namespace rclpy 65 | 66 | #endif // RCLPY__DESTROYABLE_HPP_ 67 | -------------------------------------------------------------------------------- /rclpy/test/test_validate_node_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.exceptions import InvalidNodeNameException 18 | from rclpy.validate_node_name import validate_node_name 19 | 20 | 21 | class TestValidateNodeName(unittest.TestCase): 22 | 23 | def test_validate_node_name(self) -> None: 24 | tests = [ 25 | 'my_node', 26 | ] 27 | for topic in tests: 28 | # Will raise if invalid 29 | validate_node_name(topic) 30 | 31 | def test_validate_node_name_failures(self) -> None: 32 | # node name must not be empty 33 | with self.assertRaisesRegex(InvalidNodeNameException, 'must not be empty'): 34 | validate_node_name('') 35 | # node name may not contain '.' 36 | with self.assertRaisesRegex(InvalidNodeNameException, 'must not contain characters'): 37 | validate_node_name('invalid_node.') 38 | # node name may not contain '?' 39 | with self.assertRaisesRegex(InvalidNodeNameException, 'must not contain characters'): 40 | validate_node_name('invalid_node?') 41 | # node name must not contain / 42 | with self.assertRaisesRegex(InvalidNodeNameException, 'must not contain characters'): 43 | validate_node_name('/invalid_node') 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/serialization.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__SERIALIZATION_HPP_ 16 | #define RCLPY__SERIALIZATION_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | namespace py = pybind11; 24 | 25 | namespace rclpy 26 | { 27 | /// Helper struct to manage an rcl_serialized_message_t lifetime. 28 | struct SerializedMessage 29 | { 30 | explicit SerializedMessage(rcutils_allocator_t allocator); 31 | 32 | ~SerializedMessage(); 33 | 34 | rcl_serialized_message_t rcl_msg; 35 | }; 36 | 37 | /// Serialize a ROS message 38 | /** 39 | * Raises RCUtilsError on failure to initialize a serialized message 40 | * Raises RMWError on serialization failure 41 | * 42 | * \param[in] pymsg an instance of a ROS message 43 | * \param[in] pymsg_type the type of the ROS message 44 | * \return serialized bytes 45 | */ 46 | py::bytes 47 | serialize(py::object pymsg, py::object pymsg_type); 48 | 49 | /// Deserialize a ROS message 50 | /** 51 | * Raises RMWError on deserialization failure 52 | * 53 | * \param[in] pybuffer a serialized ROS message 54 | * \param[in] pymsg_type the type of the ROS message to deserialize 55 | * \return an instance of a ROS message 56 | */ 57 | py::object 58 | deserialize(py::bytes pybuffer, py::object pymsg_type); 59 | } // namespace rclpy 60 | 61 | #endif // RCLPY__SERIALIZATION_HPP_ 62 | -------------------------------------------------------------------------------- /rclpy/rclpy/experimental/events_executor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024-2025 Brad Martin 2 | # Copyright 2024 Merlin Labs, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import faulthandler 16 | import typing 17 | 18 | import rclpy.executors 19 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 20 | 21 | 22 | # Try to look like we inherit from the rclpy Executor for type checking purposes without 23 | # getting any of the code from the base class. 24 | def EventsExecutor(*, context: typing.Optional[rclpy.Context] = None) -> rclpy.executors.Executor: 25 | if context is None: 26 | context = rclpy.get_default_context() 27 | 28 | # For debugging purposes, if anything goes wrong in C++ make sure we also get a 29 | # Python backtrace dumped with the crash. 30 | faulthandler.enable() 31 | 32 | ex = typing.cast(rclpy.executors.Executor, _rclpy.EventsExecutor(context)) 33 | 34 | # rclpy.Executor does this too. Note, the context itself is smart enough to check 35 | # for bound methods, and check whether the instances they're bound to still exist at 36 | # callback time, so we don't have to worry about tearing down this stale callback at 37 | # destruction time. 38 | # TODO(bmartin427) This should really be done inside of the EventsExecutor 39 | # implementation itself, but I'm unable to figure out a pybind11 incantation that 40 | # allows me to pass this bound method call from C++. 41 | context.on_shutdown(ex.wake) 42 | 43 | return ex 44 | -------------------------------------------------------------------------------- /rclpy/test/test_utilities.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.constants import S_TO_NS 18 | import rclpy.utilities 19 | 20 | 21 | class TestValidateRemoveRosArgs(unittest.TestCase): 22 | 23 | def test_remove_ros_args(self) -> None: 24 | args = [ 25 | 'process_name', 26 | '-d', 27 | '--ros-args', 28 | '-r', '__ns:=/foo/bar', 29 | '-r', '__ns:=/fiz/buz', 30 | '--', 31 | '--foo=bar', 32 | '--baz' 33 | ] 34 | stripped_args = rclpy.utilities.remove_ros_args(args=args) 35 | self.assertEqual(4, len(stripped_args)) 36 | self.assertEqual('process_name', stripped_args[0]) 37 | self.assertEqual('-d', stripped_args[1]) 38 | self.assertEqual('--foo=bar', stripped_args[2]) 39 | self.assertEqual('--baz', stripped_args[3]) 40 | 41 | 42 | class TestUtilities(unittest.TestCase): 43 | 44 | def test_timeout_sec_to_nsec(self) -> None: 45 | self.assertGreater(0, rclpy.utilities.timeout_sec_to_nsec(None)) 46 | self.assertGreater(0, rclpy.utilities.timeout_sec_to_nsec(-1)) 47 | self.assertEqual(0, rclpy.utilities.timeout_sec_to_nsec(0)) 48 | self.assertEqual(0, rclpy.utilities.timeout_sec_to_nsec(0.5 / S_TO_NS)) 49 | self.assertEqual(int(1.5 * S_TO_NS), rclpy.utilities.timeout_sec_to_nsec(1.5)) 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/events_queue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Brad Martin 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "events_executor/events_queue.hpp" 15 | 16 | namespace rclpy 17 | { 18 | namespace events_executor 19 | { 20 | 21 | EventsQueue::~EventsQueue() {} 22 | 23 | void EventsQueue::Enqueue(std::function event_handler) 24 | { 25 | { 26 | std::unique_lock lock(mutex_); 27 | queue_.push(event_handler); 28 | } 29 | cv_.notify_one(); 30 | } 31 | 32 | void EventsQueue::Run(const std::optional deadline) 33 | { 34 | while (true) { 35 | std::function handler; 36 | { 37 | std::unique_lock lock(mutex_); 38 | auto pred = [this]() {return stopped_ || !queue_.empty();}; 39 | if (deadline) { 40 | cv_.wait_until(lock, *deadline, pred); 41 | } else { 42 | cv_.wait(lock, pred); 43 | } 44 | if (stopped_ || queue_.empty()) { 45 | // We stopped for some reason other than being ready to run something (stopped or timeout) 46 | return; 47 | } 48 | handler = queue_.front(); 49 | queue_.pop(); 50 | } 51 | handler(); 52 | } 53 | } 54 | 55 | void EventsQueue::Stop() 56 | { 57 | std::unique_lock lock(mutex_); 58 | stopped_ = true; 59 | cv_.notify_one(); 60 | } 61 | 62 | void EventsQueue::Restart() 63 | { 64 | std::unique_lock lock(mutex_); 65 | stopped_ = false; 66 | } 67 | 68 | } // namespace events_executor 69 | } // namespace rclpy 70 | -------------------------------------------------------------------------------- /rclpy/test/test_python_allocator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include "python_allocator.hpp" 22 | 23 | namespace py = pybind11; 24 | 25 | TEST(test_allocator, vector) { 26 | py::scoped_interpreter guard{}; // Start a Python interpreter 27 | 28 | std::vector> container(42); 29 | 30 | EXPECT_EQ(42u, container.capacity()); 31 | ASSERT_EQ(42u, container.size()); 32 | 33 | for (int i = 0; i < 42; ++i) { 34 | container[i] = i; 35 | } 36 | } 37 | 38 | TEST(test_allocator, equality) { 39 | py::scoped_interpreter guard{}; // Start a Python interpreter 40 | 41 | rclpy::PythonAllocator int_alloc; 42 | rclpy::PythonAllocator float_alloc; 43 | 44 | EXPECT_TRUE(int_alloc == float_alloc); 45 | EXPECT_FALSE(int_alloc != float_alloc); 46 | } 47 | 48 | TEST(test_allocator, make_1) { 49 | py::scoped_interpreter guard{}; // Start a Python interpreter 50 | 51 | rclpy::PythonAllocator int_alloc; 52 | 53 | int * i = int_alloc.allocate(1); 54 | 55 | ASSERT_NE(nullptr, i); 56 | 57 | int_alloc.deallocate(i, 1); 58 | } 59 | 60 | TEST(test_allocator, copy_construct_make_1) { 61 | py::scoped_interpreter guard{}; // Start a Python interpreter 62 | 63 | rclpy::PythonAllocator float_alloc; 64 | rclpy::PythonAllocator int_alloc(float_alloc); 65 | 66 | int * i = int_alloc.allocate(1); 67 | 68 | ASSERT_NE(nullptr, i); 69 | 70 | int_alloc.deallocate(i, 1); 71 | } 72 | -------------------------------------------------------------------------------- /rclpy/test/test_type_hash.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.type_hash import TypeHash 18 | from rclpy.type_hash import TypeHashDictionary 19 | 20 | # From std_msgs/msg/String.json 21 | STD_MSGS_STRING_TYPE_HASH_DICT: TypeHashDictionary = { 22 | 'version': 1, 23 | 'value': b'\xdf\x66\x8c\x74\x04\x82\xbb\xd4\x8f\xb3\x9d\x76\xa7\x0d\xfd\x4b' 24 | b'\xd5\x9d\xb1\x28\x80\x21\x74\x35\x03\x25\x9e\x94\x8f\x6b\x1a\x18', 25 | } 26 | STD_MSGS_STRING_TYPE_HASH_STR = 'RIHS01_' \ 27 | 'df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18' 28 | 29 | 30 | class TestTypeHash(unittest.TestCase): 31 | 32 | def test_dict_constructor(self) -> None: 33 | type_hash = TypeHash(**STD_MSGS_STRING_TYPE_HASH_DICT) 34 | self.assertTrue(hasattr(type_hash, '__slots__')) 35 | self.assertEqual(STD_MSGS_STRING_TYPE_HASH_DICT['version'], type_hash.version) 36 | self.assertEqual(STD_MSGS_STRING_TYPE_HASH_DICT['value'], type_hash.value) 37 | 38 | def test_print_valid(self) -> None: 39 | actual_str = str(TypeHash(**STD_MSGS_STRING_TYPE_HASH_DICT)) 40 | expected_str = STD_MSGS_STRING_TYPE_HASH_STR 41 | self.assertEqual(expected_str, actual_str) 42 | 43 | def test_print_invalid(self) -> None: 44 | actual_str = str(TypeHash()) 45 | expected_str = 'INVALID' 46 | self.assertEqual(expected_str, actual_str) 47 | 48 | def test_equals(self) -> None: 49 | self.assertEqual(TypeHash(), TypeHash()) 50 | self.assertNotEqual(TypeHash(version=5), TypeHash()) 51 | -------------------------------------------------------------------------------- /rclpy/test/test_context.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from rclpy.context import Context 16 | 17 | 18 | def test_on_shutdown_method() -> None: 19 | context = Context() 20 | context.init() 21 | assert context.ok() 22 | 23 | callback_called = False 24 | 25 | class SomeClass: 26 | 27 | def on_shutdown(self) -> None: 28 | nonlocal callback_called 29 | callback_called = True 30 | 31 | instance = SomeClass() 32 | context.on_shutdown(instance.on_shutdown) 33 | 34 | context.shutdown() 35 | assert not context.ok() 36 | 37 | assert callback_called 38 | 39 | 40 | def test_on_shutdown_function() -> None: 41 | context = Context() 42 | context.init() 43 | assert context.ok() 44 | 45 | callback_called = False 46 | 47 | def on_shutdown() -> None: 48 | nonlocal callback_called 49 | callback_called = True 50 | 51 | context.on_shutdown(on_shutdown) 52 | 53 | context.shutdown() 54 | assert not context.ok() 55 | 56 | assert callback_called 57 | 58 | 59 | def test_context_manager() -> None: 60 | context = Context() 61 | 62 | assert not context.ok(), 'the context should not be ok() before init() is called' 63 | 64 | context.init() 65 | 66 | with context as the_context: 67 | # Make sure the correct instance is returned 68 | assert the_context is context 69 | 70 | assert context.ok(), 'the context should now be initialized' 71 | 72 | assert not context.ok(), 'the context should now be shut down' 73 | 74 | # Make sure it does not raise (smoke test) 75 | context.try_shutdown() 76 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/delayed_event_thread.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Brad Martin 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "events_executor/delayed_event_thread.hpp" 15 | 16 | #include 17 | 18 | namespace rclpy 19 | { 20 | namespace events_executor 21 | { 22 | 23 | DelayedEventThread::DelayedEventThread(EventsQueue * events_queue) 24 | : events_queue_(events_queue), thread_([this]() {RunThread();}) 25 | { 26 | } 27 | 28 | DelayedEventThread::~DelayedEventThread() 29 | { 30 | { 31 | std::unique_lock lock(mutex_); 32 | done_ = true; 33 | } 34 | cv_.notify_one(); 35 | thread_.join(); 36 | } 37 | 38 | void DelayedEventThread::EnqueueAt( 39 | std::chrono::steady_clock::time_point when, std::function handler) 40 | { 41 | { 42 | std::unique_lock lock(mutex_); 43 | when_ = when; 44 | handler_ = handler; 45 | } 46 | cv_.notify_one(); 47 | } 48 | 49 | void DelayedEventThread::RunThread() 50 | { 51 | std::unique_lock lock(mutex_); 52 | while (!done_) { 53 | if (handler_) { 54 | // Make sure we don't dispatch a stale wait if it changes while we're waiting. 55 | const auto latched_when = when_; 56 | if ( 57 | (std::cv_status::timeout == cv_.wait_until(lock, latched_when)) && handler_ && 58 | (when_ <= latched_when)) 59 | { 60 | auto handler = std::move(handler_); 61 | handler_ = {}; 62 | events_queue_->Enqueue(std::move(handler)); 63 | } 64 | } else { 65 | // Wait indefinitely until we get signaled that there's something worth looking at. 66 | cv_.wait(lock); 67 | } 68 | } 69 | } 70 | 71 | } // namespace events_executor 72 | } // namespace rclpy 73 | -------------------------------------------------------------------------------- /rclpy/test/test_validate_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.exceptions import InvalidServiceNameException 18 | from rclpy.exceptions import InvalidTopicNameException 19 | from rclpy.validate_topic_name import validate_topic_name 20 | 21 | 22 | class TestValidateTopicName(unittest.TestCase): 23 | 24 | def test_validate_topic_name(self) -> None: 25 | tests = [ 26 | 'chatter', 27 | '{node}/chatter', 28 | '~/chatter', 29 | ] 30 | for topic in tests: 31 | # Will raise if invalid 32 | validate_topic_name(topic) 33 | 34 | def test_validate_topic_name_failures(self) -> None: 35 | # topic name may not contain '?' 36 | with self.assertRaisesRegex(InvalidTopicNameException, 'must not contain characters'): 37 | validate_topic_name('/invalid_topic?') 38 | # topic name must start with number in any token 39 | with self.assertRaisesRegex(InvalidTopicNameException, 'must not start with a number'): 40 | validate_topic_name('invalid/42topic') 41 | 42 | def test_validate_topic_name_failures_service(self) -> None: 43 | # service name may not contain '?' 44 | with self.assertRaisesRegex(InvalidServiceNameException, 'must not contain characters'): 45 | validate_topic_name('/invalid_service?', is_service=True) 46 | # service name must start with number in any token 47 | with self.assertRaisesRegex(InvalidServiceNameException, 'must not start with a number'): 48 | validate_topic_name('invalid/42service', is_service=True) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /rclpy/test/test_validate_full_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.exceptions import InvalidServiceNameException 18 | from rclpy.exceptions import InvalidTopicNameException 19 | from rclpy.validate_full_topic_name import validate_full_topic_name 20 | 21 | 22 | class TestValidateFullTopicName(unittest.TestCase): 23 | 24 | def test_validate_full_topic_name(self) -> None: 25 | tests = [ 26 | '/chatter', 27 | '/node_name/chatter', 28 | '/ns/node_name/chatter', 29 | ] 30 | for topic in tests: 31 | # Will raise if invalid 32 | validate_full_topic_name(topic) 33 | 34 | def test_validate_full_topic_name_failures(self) -> None: 35 | # topic name may not contain '?' 36 | with self.assertRaisesRegex(InvalidTopicNameException, 'must not contain characters'): 37 | validate_full_topic_name('/invalid_topic?') 38 | # topic name must start with / 39 | with self.assertRaisesRegex(InvalidTopicNameException, 'must be absolute'): 40 | validate_full_topic_name('invalid_topic') 41 | 42 | def test_validate_full_topic_name_failures_services(self) -> None: 43 | # service name may not contain '?' 44 | with self.assertRaisesRegex(InvalidServiceNameException, 'must not contain characters'): 45 | validate_full_topic_name('/invalid_service?', is_service=True) 46 | # service name must start with / 47 | with self.assertRaisesRegex(InvalidServiceNameException, 'must be absolute'): 48 | validate_full_topic_name('invalid_service', is_service=True) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/delayed_event_thread.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Brad Martin 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__EVENTS_EXECUTOR__DELAYED_EVENT_THREAD_HPP_ 16 | #define RCLPY__EVENTS_EXECUTOR__DELAYED_EVENT_THREAD_HPP_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "events_executor/events_queue.hpp" 25 | 26 | namespace rclpy 27 | { 28 | namespace events_executor 29 | { 30 | 31 | /// This object manages posting an event handler to an EventsQueue after a specified delay. The 32 | /// current delay may be changed or canceled at any time. This is done by way of a self-contained 33 | /// child thread to perform the wait. 34 | class DelayedEventThread 35 | { 36 | public: 37 | /// The pointer is aliased and must live for the lifetime of this object. 38 | explicit DelayedEventThread(EventsQueue *); 39 | ~DelayedEventThread(); 40 | 41 | /// Schedules an event handler to be enqueued at the specified time point. Replaces any previous 42 | /// wait and handler, which will never be dispatched if it has not been already. 43 | void EnqueueAt(std::chrono::steady_clock::time_point when, std::function handler); 44 | 45 | /// Cancels any previously-scheduled handler. 46 | void Cancel() {EnqueueAt({}, {});} 47 | 48 | private: 49 | void RunThread(); 50 | 51 | EventsQueue * const events_queue_; 52 | std::mutex mutex_; 53 | bool done_{}; 54 | std::condition_variable cv_; 55 | std::chrono::steady_clock::time_point when_; 56 | std::function handler_; 57 | std::thread thread_; 58 | }; 59 | 60 | } // namespace events_executor 61 | } // namespace rclpy 62 | 63 | #endif // RCLPY__EVENTS_EXECUTOR__DELAYED_EVENT_THREAD_HPP_ 64 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/clock_event.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__CLOCK_EVENT_HPP_ 16 | #define RCLPY__CLOCK_EVENT_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "clock.hpp" 27 | 28 | namespace py = pybind11; 29 | 30 | namespace rclpy 31 | { 32 | class ClockEvent 33 | { 34 | public: 35 | /// Wait until a time specified by a system or steady clock. 36 | /// \param clock the clock to use for time synchronization with until 37 | /// \param until this method will block until this time is reached. 38 | template 39 | void wait_until(std::shared_ptr clock, rcl_time_point_t until); 40 | 41 | /// Wait until a time specified by a ROS clock. 42 | /// \warning the caller is responsible for creating a time jump callback to set this event when 43 | /// the target ROS time is reached. 44 | /// when a given ROS time is reached. 45 | /// \param clock the clock to use for time synchronization. 46 | /// \param until this method will block until this time is reached. 47 | void wait_until_ros(std::shared_ptr clock, rcl_time_point_t until); 48 | 49 | /// Indicate if the ClockEvent is set. 50 | /// \return True if the ClockEvent is set. 51 | bool is_set(); 52 | 53 | /// Set the event. 54 | void set(); 55 | 56 | /// Clear the event. 57 | void clear(); 58 | 59 | private: 60 | bool state_ = false; 61 | std::mutex mutex_; 62 | std::condition_variable cv_; 63 | }; 64 | 65 | /// Define a pybind11 wrapper for an rclpy::ClockEvent 66 | void define_clock_event(py::object module); 67 | } // namespace rclpy 68 | 69 | #endif // RCLPY__CLOCK_EVENT_HPP_ 70 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/guard_condition.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__GUARD_CONDITION_HPP_ 16 | #define RCLPY__GUARD_CONDITION_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "context.hpp" 25 | #include "destroyable.hpp" 26 | #include "utils.hpp" 27 | 28 | namespace py = pybind11; 29 | 30 | namespace rclpy 31 | { 32 | /// Create a general purpose guard condition 33 | class GuardCondition : public Destroyable, public std::enable_shared_from_this 34 | { 35 | public: 36 | /** 37 | * Raises RuntimeError if initializing the guard condition fails 38 | */ 39 | explicit GuardCondition(Context & context); 40 | 41 | /// Trigger a general purpose guard condition 42 | /** 43 | * Raises ValueError if pygc is not a guard condition capsule 44 | * Raises RCLError if the guard condition could not be triggered 45 | */ 46 | void 47 | trigger_guard_condition(); 48 | 49 | /// Get rcl_guard_condition_t pointer 50 | rcl_guard_condition_t * rcl_ptr() const 51 | { 52 | return rcl_guard_condition_.get(); 53 | } 54 | 55 | /// Force an early destruction of this object 56 | void destroy() override; 57 | 58 | private: 59 | Context context_; 60 | std::shared_ptr rcl_guard_condition_; 61 | 62 | /// Handle destructor for guard condition 63 | static void 64 | _rclpy_destroy_guard_condition(void * p) 65 | { 66 | (void)p; 67 | // Empty destructor, the class should take care of the lifecycle. 68 | } 69 | }; 70 | 71 | /// Define a pybind11 wrapper for an rclpy::GuardCondition 72 | void define_guard_condition(py::object module); 73 | } // namespace rclpy 74 | 75 | #endif // RCLPY__GUARD_CONDITION_HPP_ 76 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/type_description_service.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ 16 | #define RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include "destroyable.hpp" 23 | #include "service.hpp" 24 | 25 | namespace py = pybind11; 26 | 27 | namespace rclpy 28 | { 29 | 30 | class TypeDescriptionService 31 | : public Destroyable, public std::enable_shared_from_this 32 | { 33 | public: 34 | /// Initialize and contain the rcl implementation of ~/get_type_description 35 | /** 36 | * \param[in] node Node to add the service to 37 | */ 38 | explicit TypeDescriptionService(Node & node); 39 | 40 | ~TypeDescriptionService() = default; 41 | 42 | /// Return the wrapped rcl service, so that it can be added to the node waitsets 43 | /** 44 | * \return The capsule containing the Service 45 | */ 46 | Service 47 | get_impl(); 48 | 49 | /// Handle an incoming request to the service 50 | /** 51 | * \param[in] pyrequest incoming request to handle 52 | * \param[in] pyresponse_type Python type of the response object to wrap the C message in 53 | * \param[in] node The node that this service belongs to 54 | * \return response message to send 55 | */ 56 | py::object 57 | handle_request(py::object pyrequest, py::object pyresponse_type, Node & node); 58 | 59 | /// Force early cleanup of object 60 | void 61 | destroy() override; 62 | 63 | private: 64 | std::shared_ptr service_; 65 | }; 66 | 67 | /// Define a pybind11 wrapper for an rclpy::TypeDescriptionService 68 | void 69 | define_type_description_service(py::object module); 70 | } // namespace rclpy 71 | 72 | #endif // RCLPY__TYPE_DESCRIPTION_SERVICE_HPP_ 73 | -------------------------------------------------------------------------------- /rclpy/test/test_messages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TYPE_CHECKING 16 | import unittest 17 | 18 | import rclpy 19 | 20 | import rclpy.context 21 | from rclpy.serialization import serialize_message 22 | from test_msgs.msg import BasicTypes, Strings 23 | 24 | 25 | class TestMessages(unittest.TestCase): 26 | 27 | if TYPE_CHECKING: 28 | context: rclpy.context.Context 29 | node: rclpy.node.Node 30 | 31 | NODE_NAME = 'messages_tester' 32 | NAMESPACE = 'messages_test' 33 | 34 | @classmethod 35 | def setUpClass(cls) -> None: 36 | cls.context = rclpy.context.Context() 37 | rclpy.init(context=cls.context) 38 | cls.node = rclpy.create_node( 39 | TestMessages.NODE_NAME, 40 | namespace=TestMessages.NAMESPACE, 41 | context=cls.context 42 | ) 43 | 44 | @classmethod 45 | def tearDownClass(cls) -> None: 46 | cls.node.destroy_node() 47 | rclpy.shutdown(context=cls.context) 48 | 49 | def test_unicode_string(self) -> None: 50 | msg = Strings() 51 | msg.string_value = 'ñu' 52 | pub = self.node.create_publisher(Strings, 'chatter', 1) 53 | pub.publish(msg) 54 | self.node.destroy_publisher(pub) 55 | 56 | def test_different_type_raises(self) -> None: 57 | pub = self.node.create_publisher( 58 | BasicTypes, 'chatter_different_message_type', 1) 59 | with self.assertRaises(TypeError): 60 | pub.publish('different message type') 61 | self.node.destroy_publisher(pub) 62 | 63 | def test_serialized_publish(self) -> None: 64 | msg = Strings() 65 | msg.string_value = 'ñu' 66 | pub = self.node.create_publisher(Strings, 'chatter', 1) 67 | pub.publish(serialize_message(msg)) 68 | self.node.destroy_publisher(pub) 69 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/python_allocator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__PYTHON_ALLOCATOR_HPP_ 16 | #define RCLPY__PYTHON_ALLOCATOR_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | namespace rclpy 23 | { 24 | //----------- Declaration ---------- 25 | 26 | /// Allocate memory using Python's allocator 27 | /** 28 | * \warning The GIL must be held while the allocator is used. 29 | */ 30 | template 31 | struct PythonAllocator 32 | { 33 | using value_type = T; 34 | 35 | PythonAllocator() noexcept = default; 36 | 37 | template 38 | PythonAllocator(const PythonAllocator & /* other */) noexcept {} 39 | 40 | T * allocate(std::size_t n); 41 | 42 | void deallocate(T * p, std::size_t n); 43 | }; 44 | 45 | template 46 | constexpr bool operator==(const PythonAllocator &, const PythonAllocator &) noexcept; 47 | 48 | template 49 | constexpr bool operator!=(const PythonAllocator &, const PythonAllocator &) noexcept; 50 | 51 | //----------- Implementation ---------- 52 | 53 | template 54 | T * PythonAllocator::allocate(std::size_t n) 55 | { 56 | T * ptr = static_cast(PyMem_Malloc(n * sizeof(T))); 57 | if (nullptr == ptr) { 58 | throw std::bad_alloc(); 59 | } 60 | return ptr; 61 | } 62 | 63 | template 64 | void PythonAllocator::deallocate(T * p, std::size_t /* n */) 65 | { 66 | PyMem_Free(p); 67 | } 68 | 69 | template 70 | constexpr bool operator==(const PythonAllocator &, const PythonAllocator &) noexcept 71 | { 72 | return true; 73 | } 74 | 75 | template 76 | constexpr bool operator!=(const PythonAllocator &, const PythonAllocator &) noexcept 77 | { 78 | return false; 79 | } 80 | } // namespace rclpy 81 | 82 | #endif // RCLPY__PYTHON_ALLOCATOR_HPP_ 83 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/events_queue.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Brad Martin 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__EVENTS_EXECUTOR__EVENTS_QUEUE_HPP_ 16 | #define RCLPY__EVENTS_EXECUTOR__EVENTS_QUEUE_HPP_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace rclpy 26 | { 27 | namespace events_executor 28 | { 29 | 30 | /// This class represents a queue of event handlers to dispatch. Handlers may be enqueued from any 31 | /// thread, and will always be dispatched during a call to a Run*() method on the thread invoking 32 | /// that method. Multiple threads may not invoke Run*() methods simultaneously. 33 | class EventsQueue 34 | { 35 | public: 36 | ~EventsQueue(); 37 | 38 | /// Add an event handler to the queue to be dispatched. Can be invoked by any thread. 39 | void Enqueue(std::function); 40 | 41 | /// Run all ready event handlers, and any that become ready before the given deadline. Calling 42 | /// Stop() will make this return immediately even if ready handlers are enqueued. 43 | void Run(const std::optional = {}); 44 | 45 | /// Causes any Run*() methods outstanding to return immediately. Can be invoked from any thread. 46 | /// The stopped condition persists (causing any *subsequent* Run*() calls to also return) until 47 | /// Restart() is invoked. 48 | void Stop(); 49 | 50 | /// Ends a previous stopped condition, allowing Run*() methods to operate again. May be invoked 51 | /// from any thread. 52 | void Restart(); 53 | 54 | private: 55 | std::queue> queue_; 56 | std::mutex mutex_; 57 | std::condition_variable cv_; 58 | bool stopped_{}; 59 | }; 60 | 61 | } // namespace events_executor 62 | } // namespace rclpy 63 | 64 | #endif // RCLPY__EVENTS_EXECUTOR__EVENTS_QUEUE_HPP_ 65 | -------------------------------------------------------------------------------- /rclpy/rclpy/type_hash.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TypedDict 16 | 17 | 18 | class TypeHashDictionary(TypedDict): 19 | version: int 20 | value: bytes 21 | 22 | 23 | class TypeHash: 24 | """Type hash.""" 25 | 26 | _TYPE_HASH_SIZE = 32 27 | 28 | # ros2cli needs __slots__ to avoid API break from https://github.com/ros2/rclpy/pull/1243. 29 | # __slots__ is also used improve performance of Python objects. 30 | __slots__ = [ 31 | '_version', 32 | '_value', 33 | ] 34 | 35 | def __init__(self, version: int = -1, value: bytes = bytes(_TYPE_HASH_SIZE)): 36 | self.version = version 37 | self.value = value 38 | 39 | @property 40 | def version(self) -> int: 41 | """ 42 | Get field 'version'. 43 | 44 | :returns: version attribute 45 | """ 46 | return self._version 47 | 48 | @version.setter 49 | def version(self, value: int) -> None: 50 | assert isinstance(value, int) 51 | self._version = value 52 | 53 | @property 54 | def value(self) -> bytes: 55 | """ 56 | Get field 'value'. 57 | 58 | :returns: value attribute 59 | """ 60 | return self._value 61 | 62 | @value.setter 63 | def value(self, value: bytes) -> None: 64 | assert isinstance(value, bytes) 65 | self._value = value 66 | 67 | def __eq__(self, other: object) -> bool: 68 | if not isinstance(other, TypeHash): 69 | return False 70 | return all( 71 | self.__getattribute__(slot) == other.__getattribute__(slot) 72 | for slot in self.__slots__) 73 | 74 | def __str__(self) -> str: 75 | if self._version <= 0 or len(self._value) != self._TYPE_HASH_SIZE: 76 | return 'INVALID' 77 | 78 | return f'RIHS{self._version:02}_{self._value.hex()}' 79 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/exceptions.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__EXCEPTIONS_HPP_ 16 | #define RCLPY__EXCEPTIONS_HPP_ 17 | 18 | #include 19 | #include 20 | 21 | namespace rclpy 22 | { 23 | std::string append_rcutils_error(std::string prepend); 24 | 25 | std::string append_rcl_error(std::string prepend); 26 | 27 | std::string append_rmw_error(std::string prepend); 28 | 29 | class RCUtilsError : public std::runtime_error 30 | { 31 | public: 32 | explicit RCUtilsError(const std::string & error_text); 33 | 34 | ~RCUtilsError() = default; 35 | }; 36 | 37 | class RCLError : public std::runtime_error 38 | { 39 | public: 40 | explicit RCLError(const std::string & error_text); 41 | 42 | ~RCLError() = default; 43 | }; 44 | 45 | class RMWError : public std::runtime_error 46 | { 47 | public: 48 | explicit RMWError(const std::string & error_text); 49 | 50 | ~RMWError() = default; 51 | }; 52 | 53 | class RCLInvalidROSArgsError : public RCLError 54 | { 55 | using RCLError::RCLError; 56 | }; 57 | 58 | class UnknownROSArgsError : public std::runtime_error 59 | { 60 | using std::runtime_error::runtime_error; 61 | }; 62 | 63 | class NodeNameNonExistentError : public RCLError 64 | { 65 | using RCLError::RCLError; 66 | }; 67 | 68 | class UnsupportedEventTypeError : public RCLError 69 | { 70 | using RCLError::RCLError; 71 | }; 72 | 73 | class NotImplementedError : public RCLError 74 | { 75 | using RCLError::RCLError; 76 | }; 77 | 78 | class TimerCancelledError : public RCLError 79 | { 80 | using RCLError::RCLError; 81 | }; 82 | 83 | class InvalidHandle : public std::runtime_error 84 | { 85 | using std::runtime_error::runtime_error; 86 | }; 87 | 88 | class ContextAlreadyShutdown : public std::runtime_error 89 | { 90 | using std::runtime_error::runtime_error; 91 | }; 92 | 93 | } // namespace rclpy 94 | 95 | #endif // RCLPY__EXCEPTIONS_HPP_ 96 | -------------------------------------------------------------------------------- /rclpy/rclpy/lifecycle/publisher.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import annotations 16 | 17 | from typing import Generic, Tuple, Type, TYPE_CHECKING, TypedDict, Union 18 | 19 | from rclpy.callback_groups import CallbackGroup 20 | from rclpy.event_handler import PublisherEventCallbacks 21 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 22 | from rclpy.publisher import Publisher 23 | from rclpy.qos import QoSProfile 24 | from rclpy.type_support import MsgT 25 | 26 | from typing_extensions import TypeAlias 27 | from typing_extensions import Unpack 28 | 29 | from .managed_entity import SimpleManagedEntity 30 | 31 | if TYPE_CHECKING: 32 | LifecyclePublisherArgs: TypeAlias = Tuple[_rclpy.Publisher[MsgT], Type[MsgT], str, QoSProfile, 33 | PublisherEventCallbacks, CallbackGroup] 34 | 35 | class LifecyclePublisherKWArgs(TypedDict, Generic[MsgT]): 36 | publisher_impl: _rclpy.Publisher[MsgT] 37 | msg_type: Type[MsgT] 38 | topic: str 39 | qos_profile: QoSProfile 40 | event_callbacks: PublisherEventCallbacks 41 | callback_group: CallbackGroup 42 | 43 | 44 | class LifecyclePublisher(SimpleManagedEntity, Publisher[MsgT]): 45 | """Managed publisher entity.""" 46 | 47 | def __init__( 48 | self, 49 | *args: 'Unpack[LifecyclePublisherArgs[MsgT]]', 50 | **kwargs: 'Unpack[LifecyclePublisherKWArgs[MsgT]]' 51 | ) -> None: 52 | SimpleManagedEntity.__init__(self) 53 | Publisher.__init__(self, *args, **kwargs) 54 | 55 | @SimpleManagedEntity.when_enabled 56 | def publish(self, msg: Union[MsgT, bytes]) -> None: 57 | """ 58 | Publish a message if the lifecycle publisher is enabled. 59 | 60 | See rclpy.publisher.Publisher.publish() for more details. 61 | """ 62 | Publisher.publish(self, msg) 63 | -------------------------------------------------------------------------------- /rclpy/test/test_expand_topic_name.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rclpy.expand_topic_name import expand_topic_name 18 | 19 | 20 | class TestExpandTopicName(unittest.TestCase): 21 | 22 | def test_expand_topic_name(self) -> None: 23 | tests = { 24 | # 'expected output': ['input topic', 'node', '/ns'] 25 | '/ns/chatter': ['chatter', 'node_name', '/ns'], 26 | '/chatter': ['chatter', 'node_name', '/'], 27 | '/ns/node_name/chatter': ['~/chatter', 'node_name', '/ns'], 28 | '/node_name/chatter': ['~/chatter', 'node_name', '/'], 29 | } 30 | for expected_topic, input_tuple in tests.items(): 31 | topic, node, namespace = input_tuple 32 | expanded_topic = expand_topic_name(topic, node, namespace) 33 | self.assertEqual(expanded_topic, expected_topic) 34 | 35 | def test_expand_topic_name_invalid_node_name(self) -> None: 36 | # node name may not contain '?' 37 | with self.assertRaisesRegex(ValueError, 'node name'): 38 | expand_topic_name('topic', 'invalid_node_name?', '/ns') 39 | 40 | def test_expand_topic_name_invalid_namespace_empty(self) -> None: 41 | # namespace may not be empty 42 | with self.assertRaisesRegex(ValueError, 'namespace'): 43 | expand_topic_name('topic', 'node_name', '') 44 | 45 | def test_expand_topic_name_invalid_namespace_relative(self) -> None: 46 | # namespace may not be relative 47 | with self.assertRaisesRegex(ValueError, 'namespace'): 48 | expand_topic_name('topic', 'node_name', 'ns') 49 | 50 | def test_expand_topic_name_invalid_topic(self) -> None: 51 | # topic may not contain '?' 52 | with self.assertRaisesRegex(ValueError, 'topic name'): 53 | expand_topic_name('invalid/topic?', 'node_name', '/ns') 54 | 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/destroyable.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include "destroyable.hpp" 21 | #include "exceptions.hpp" 22 | 23 | namespace rclpy 24 | { 25 | Destroyable::Destroyable(const Destroyable &) 26 | { 27 | // When a destroyable is copied, it does not matter if someone asked 28 | // to destroy the original. The copy has its own lifetime. 29 | use_count = 0; 30 | please_destroy_ = false; 31 | } 32 | 33 | void 34 | Destroyable::enter() 35 | { 36 | if (please_destroy_) { 37 | throw InvalidHandle("cannot use Destroyable because destruction was requested"); 38 | } 39 | ++use_count; 40 | } 41 | 42 | void 43 | Destroyable::exit(py::object, py::object, py::object) 44 | { 45 | if (0u == use_count) { 46 | throw std::runtime_error("Internal error: Destroyable use_count would be negative"); 47 | } 48 | 49 | --use_count; 50 | if (please_destroy_ && 0u == use_count) { 51 | destroy(); 52 | } 53 | } 54 | 55 | void 56 | Destroyable::destroy() 57 | { 58 | // Normally would be pure virtual, but then pybind11 can't create bindings for this class 59 | throw NotImplementedError("Internal error: Destroyable subclass didn't override destroy()"); 60 | } 61 | 62 | void 63 | Destroyable::destroy_when_not_in_use() 64 | { 65 | if (please_destroy_) { 66 | // already asked to destroy 67 | return; 68 | } 69 | please_destroy_ = true; 70 | if (0u == use_count) { 71 | destroy(); 72 | } 73 | } 74 | 75 | void 76 | define_destroyable(py::object module) 77 | { 78 | py::class_>(module, "Destroyable") 79 | .def("__enter__", &Destroyable::enter) 80 | .def("__exit__", &Destroyable::exit) 81 | .def( 82 | "destroy_when_not_in_use", &Destroyable::destroy_when_not_in_use, 83 | "Forcefully destroy the rcl object as soon as it's not actively being used"); 84 | } 85 | } // namespace rclpy 86 | -------------------------------------------------------------------------------- /rclpy/rclpy/logging.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from pathlib import Path 17 | from typing import Union 18 | 19 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 20 | from rclpy.impl.logging_severity import LoggingSeverity as LoggingSeverity 21 | from rclpy.impl.rcutils_logger import RcutilsLogger 22 | 23 | 24 | _root_logger = RcutilsLogger() 25 | 26 | 27 | def get_logger(name: str) -> RcutilsLogger: 28 | if not name: 29 | raise ValueError('Logger name must not be empty.') 30 | return _root_logger.get_child(name) 31 | 32 | 33 | def initialize() -> None: 34 | _rclpy.rclpy_logging_initialize() 35 | 36 | 37 | def shutdown() -> None: 38 | _rclpy.rclpy_logging_shutdown() 39 | 40 | 41 | def clear_config() -> None: 42 | """Clear the configuration of the logging system, e.g. logger levels.""" 43 | shutdown() 44 | initialize() 45 | 46 | 47 | def set_logger_level(name: str, level: Union[int, LoggingSeverity], 48 | detailed_error: bool = False) -> None: 49 | level = LoggingSeverity(level) 50 | _rclpy.rclpy_logging_set_logger_level(name, level, detailed_error) 51 | 52 | 53 | def get_logger_effective_level(name: str) -> LoggingSeverity: 54 | logger_level = _rclpy.rclpy_logging_get_logger_effective_level(name) 55 | return LoggingSeverity(logger_level) 56 | 57 | 58 | def get_logger_level(name: str) -> LoggingSeverity: 59 | logger_level = _rclpy.rclpy_logging_get_logger_level(name) 60 | return LoggingSeverity(logger_level) 61 | 62 | 63 | def get_logging_severity_from_string(log_severity: str) -> LoggingSeverity: 64 | return LoggingSeverity( 65 | _rclpy.rclpy_logging_severity_level_from_string(log_severity)) 66 | 67 | 68 | def get_logging_directory() -> Path: 69 | """ 70 | Return the current logging directory being used. 71 | 72 | For more details, :func:`~rcl_logging_interface.rcl_logging_get_logging_directory` 73 | """ 74 | return Path(_rclpy.rclpy_logging_get_logging_directory()) 75 | -------------------------------------------------------------------------------- /rclpy/rclpy/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Optional, TYPE_CHECKING 15 | 16 | from rclpy.exceptions import InvalidHandle 17 | from rclpy.guard_condition import GuardCondition 18 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 19 | 20 | if TYPE_CHECKING: 21 | from typing import TypeAlias 22 | 23 | from rclpy.context import Context 24 | 25 | # re-export SignalHandlerOptions enum 26 | SignalHandlerOptions: 'TypeAlias' = _rclpy.SignalHandlerOptions 27 | 28 | 29 | def install_signal_handlers(options: SignalHandlerOptions = SignalHandlerOptions.ALL) -> None: 30 | """ 31 | Install rclpy signal handlers. 32 | 33 | :param options: Indicate if to install sigint, sigterm, both or no signal handler. 34 | """ 35 | _rclpy.install_signal_handlers(options) 36 | 37 | 38 | def get_current_signal_handlers_options() -> SignalHandlerOptions: 39 | """ 40 | Get current signal handler options. 41 | 42 | :return: rclpy.signals.SignalHandlerOptions instance. 43 | """ 44 | return _rclpy.get_current_signal_handlers_options() 45 | 46 | 47 | def uninstall_signal_handlers() -> None: 48 | """Uninstall the rclpy signal handlers.""" 49 | _rclpy.uninstall_signal_handlers() 50 | 51 | 52 | class SignalHandlerGuardCondition(GuardCondition): 53 | 54 | def __init__(self, context: 'Optional[Context]' = None) -> None: 55 | super().__init__(callback=None, callback_group=None, context=context) 56 | with self.handle: 57 | _rclpy.register_sigint_guard_condition(self.handle) 58 | 59 | def __del__(self) -> None: 60 | try: 61 | self.destroy() 62 | except InvalidHandle: 63 | # already destroyed 64 | pass 65 | except ValueError: 66 | # Guard condition was not registered 67 | pass 68 | 69 | def destroy(self) -> None: 70 | with self.handle: 71 | _rclpy.unregister_sigint_guard_condition(self.handle) 72 | super().destroy() 73 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/logging.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "exceptions.hpp" 28 | #include "logging.hpp" 29 | 30 | namespace rclpy 31 | { 32 | LoggingGuard::LoggingGuard() 33 | : guard_(logging_mutex_) 34 | { 35 | } 36 | 37 | // Initialize logging mutex 38 | std::recursive_mutex LoggingGuard::logging_mutex_; 39 | 40 | static 41 | void 42 | rclpy_thread_safe_logging_output_handler( 43 | const rcutils_log_location_t * location, 44 | int severity, 45 | const char * name, 46 | rcutils_time_point_value_t timestamp, 47 | const char * format, 48 | va_list * args) 49 | { 50 | try { 51 | rclpy::LoggingGuard scoped_logging_guard; 52 | rcl_logging_multiple_output_handler(location, severity, name, timestamp, format, args); 53 | } catch (const std::exception & ex) { 54 | RCUTILS_SAFE_FWRITE_TO_STDERR("rclpy failed to get the global logging mutex: "); 55 | RCUTILS_SAFE_FWRITE_TO_STDERR(ex.what()); 56 | RCUTILS_SAFE_FWRITE_TO_STDERR("\n"); 57 | } catch (...) { 58 | RCUTILS_SAFE_FWRITE_TO_STDERR("rclpy failed to get the global logging mutex\n"); 59 | } 60 | } 61 | 62 | void 63 | logging_configure(Context & context) 64 | { 65 | rcl_allocator_t allocator = rcl_get_default_allocator(); 66 | 67 | rclpy::LoggingGuard scoped_logging_guard; 68 | rcl_ret_t ret = rcl_logging_configure_with_output_handler( 69 | &context.rcl_ptr()->global_arguments, 70 | &allocator, 71 | rclpy_thread_safe_logging_output_handler); 72 | if (RCL_RET_OK != ret) { 73 | throw RCLError("failed to initialize logging"); 74 | } 75 | } 76 | 77 | void 78 | logging_fini(void) 79 | { 80 | rclpy::LoggingGuard scoped_logging_guard; 81 | rcl_ret_t ret = rcl_logging_fini(); 82 | if (RCL_RET_OK != ret) { 83 | throw RCLError("failed to fini logging"); 84 | } 85 | } 86 | } // namespace rclpy 87 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/type_description_service.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "type_description_service.hpp" 25 | #include "utils.hpp" 26 | 27 | namespace rclpy 28 | { 29 | 30 | TypeDescriptionService::TypeDescriptionService(Node & node) 31 | { 32 | auto srv_ptr = std::make_shared(); 33 | rcl_ret_t ret = rcl_node_type_description_service_init(srv_ptr.get(), node.rcl_ptr()); 34 | if (RCL_RET_OK != ret) { 35 | throw RCLError("Failed to initialize type description service"); 36 | } 37 | service_ = std::make_shared(node, srv_ptr); 38 | } 39 | 40 | Service TypeDescriptionService::get_impl() 41 | { 42 | return *service_; 43 | } 44 | 45 | py::object TypeDescriptionService::handle_request( 46 | py::object pyrequest, 47 | py::object pyresponse_type, 48 | Node & node) 49 | { 50 | // Header not used by handler, just needed as part of signature. 51 | rmw_request_id_t header; 52 | type_description_interfaces__srv__GetTypeDescription_Response response; 53 | auto request = convert_from_py(pyrequest); 54 | rcl_node_type_description_service_handle_request( 55 | node.rcl_ptr(), 56 | &header, 57 | static_cast(request.get()), 58 | &response); 59 | return convert_to_py(&response, pyresponse_type); 60 | } 61 | 62 | void TypeDescriptionService::destroy() 63 | { 64 | service_.reset(); 65 | } 66 | 67 | void 68 | define_type_description_service(py::object module) 69 | { 70 | py::class_ 71 | >(module, "TypeDescriptionService") 72 | .def(py::init()) 73 | .def_property_readonly( 74 | "impl", &TypeDescriptionService::get_impl, "Get the rcl service wrapper capsule.") 75 | .def( 76 | "handle_request", &TypeDescriptionService::handle_request, 77 | "Handle an incoming request by calling RCL implementation"); 78 | } 79 | } // namespace rclpy 80 | -------------------------------------------------------------------------------- /rclpy/rclpy/action/graph.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import List 16 | from typing import Tuple 17 | 18 | from rclpy.node import Node 19 | 20 | 21 | def get_action_client_names_and_types_by_node( 22 | node: Node, 23 | remote_node_name: str, 24 | remote_node_namespace: str 25 | ) -> List[Tuple[str, List[str]]]: 26 | """ 27 | Get a list of action names and types for action clients associated with a node. 28 | 29 | :param node: The node used for discovery. 30 | :param remote_node_name: The name of a remote node to get action clients for. 31 | :param node_namespace: Namespace of the remote node. 32 | :return: List of tuples. 33 | The first element of each tuple is the action name and the second element is a list of 34 | action types. 35 | """ 36 | with node.handle: 37 | return node.handle.get_action_client_names_and_types_by_node( 38 | remote_node_name, remote_node_namespace) 39 | 40 | 41 | def get_action_server_names_and_types_by_node( 42 | node: Node, 43 | remote_node_name: str, 44 | remote_node_namespace: str 45 | ) -> List[Tuple[str, List[str]]]: 46 | """ 47 | Get a list of action names and types for action servers associated with a node. 48 | 49 | :param node: The node used for discovery. 50 | :param remote_node_name: The name of a remote node to get action servers for. 51 | :param node_namespace: Namespace of the remote node. 52 | :return: List of tuples. 53 | The first element of each tuple is the action name and the second element is a list of 54 | action types. 55 | """ 56 | with node.handle: 57 | return node.handle.get_action_server_names_and_types_by_node( 58 | remote_node_name, remote_node_namespace) 59 | 60 | 61 | def get_action_names_and_types(node: Node) -> List[Tuple[str, List[str]]]: 62 | """ 63 | Get a list of action names and types. 64 | 65 | :param node: The node used for discovery. 66 | :return: List of action names and types in the ROS graph as tuples. 67 | The first element of each tuple is the action name and the second element is a list of 68 | action types. 69 | """ 70 | with node.handle: 71 | return node.handle.get_action_names_and_types() 72 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/action_goal_handle.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__ACTION_GOAL_HANDLE_HPP_ 16 | #define RCLPY__ACTION_GOAL_HANDLE_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "action_server.hpp" 26 | #include "destroyable.hpp" 27 | 28 | namespace py = pybind11; 29 | 30 | namespace rclpy 31 | { 32 | 33 | class ActionGoalHandle : public Destroyable, public std::enable_shared_from_this 34 | { 35 | public: 36 | /// Create an action goal handle 37 | /** 38 | * This function will create an action goal handle for the given info message from the action server. 39 | * This action goal handle will use the typesupport defined in the service module 40 | * provided as pysrv_type to send messages. 41 | * 42 | * Raises RCLError if the action goal handle could not be created 43 | * 44 | * \param[in] pyaction_server handle to the action server that is accepting the goal 45 | * \param[in] pygoal_info_msg a message containing info about the goal being accepted 46 | */ 47 | ActionGoalHandle(rclpy::ActionServer & action_server, py::object pygoal_info_msg); 48 | 49 | ~ActionGoalHandle() = default; 50 | 51 | rcl_action_goal_state_t 52 | get_status(); 53 | 54 | void 55 | update_goal_state(rcl_action_goal_event_t event); 56 | 57 | /// Check if the goal is still active 58 | bool 59 | is_active() 60 | { 61 | return rcl_action_goal_handle_is_active(rcl_ptr()); 62 | } 63 | 64 | /// Get rcl_action goal handle_t pointer 65 | rcl_action_goal_handle_t * 66 | rcl_ptr() const 67 | { 68 | return rcl_action_goal_handle_.get(); 69 | } 70 | 71 | /// Force an early destruction of this object 72 | void 73 | destroy() override; 74 | 75 | private: 76 | ActionServer action_server_; 77 | std::shared_ptr rcl_action_goal_handle_; 78 | }; 79 | 80 | /// Define a pybind11 wrapper for an rclpy::ActionGoalHandle 81 | /** 82 | * \param[in] module a pybind11 module to add the definition to 83 | */ 84 | void 85 | define_action_goal_handle(py::module module); 86 | } // namespace rclpy 87 | 88 | #endif // RCLPY__ACTION_GOAL_HANDLE_HPP_ 89 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/qos.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__QOS_HPP_ 16 | #define RCLPY__QOS_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | namespace py = pybind11; 23 | 24 | namespace rclpy 25 | { 26 | 27 | /// Result type for checking QoS compatibility 28 | /** 29 | * \see rclpy::qos_check_compatible() 30 | */ 31 | struct QoSCheckCompatibleResult 32 | { 33 | /// Compatibility result. 34 | rmw_qos_compatibility_type_t compatibility; 35 | 36 | /// Reason for a (possible) incompatibility. 37 | /** 38 | * Set if compatibility is RMW_QOS_COMPATIBILITY_WARNING or RMW_QOS_COMPATIBILITY_ERROR. 39 | * Not set if the QoS profiles are compatible. 40 | */ 41 | char reason[2048]; 42 | }; 43 | 44 | /// Check if two QoS profiles are compatible. 45 | /** 46 | * Two QoS profiles are compatible if a publisher and subcription 47 | * using the QoS policies can communicate with each other. 48 | * 49 | * If any policies have value "system default" or "unknown" then it is possible that 50 | * compatibility cannot be determined. 51 | * In this case, the value RMW_QOS_COMPATIBILITY_WARNING is set as part of 52 | * the returned structure. 53 | * 54 | * \param[in] publisher_qos_profile: The QoS profile for a publisher. 55 | * \param[in] subscription_qos_profile: The QoS profile for a subscription. 56 | * \return Struct with compatibility set to RMW_QOS_COMPATIBILITY_OK if the QoS profiles are 57 | * compatible, or 58 | * \return Struct with compatibility set to RMW_QOS_COMPATIBILITY_WARNING if there is a chance 59 | * the QoS profiles are not compatible, or 60 | * \return Struct with compatibility set to RMW_QOS_COMPATIBILITY_ERROR if the QoS profiles are 61 | * not compatible. 62 | * \throws RMWError if an unexpected error occurs. 63 | */ 64 | QoSCheckCompatibleResult 65 | qos_check_compatible( 66 | const rmw_qos_profile_t & publisher_qos_profile, 67 | const rmw_qos_profile_t & subscription_qos_profile 68 | ); 69 | 70 | /// Define a pybind11 wrapper for an rmw_qos_profile_t 71 | /** 72 | * \param[in] module a pybind11 module to add the definition to 73 | */ 74 | void 75 | define_rmw_qos_profile(py::object module); 76 | 77 | } // namespace rclpy 78 | 79 | #endif // RCLPY__QOS_HPP_ 80 | -------------------------------------------------------------------------------- /rclpy/rclpy/guard_condition.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Callable 16 | from typing import Coroutine 17 | from typing import Optional 18 | from typing import Union 19 | 20 | from rclpy.callback_groups import CallbackGroup 21 | from rclpy.context import Context 22 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 23 | from rclpy.utilities import get_default_context 24 | from typing_extensions import TypeAlias 25 | 26 | GuardConditionCallbackType: TypeAlias = Callable[[], Union[Coroutine[None, None, None], None]] 27 | 28 | 29 | class GuardCondition: 30 | 31 | def __init__(self, callback: Optional[GuardConditionCallbackType], 32 | callback_group: Optional[CallbackGroup], 33 | context: Optional[Context] = None) -> None: 34 | """ 35 | Create a GuardCondition. 36 | 37 | .. warning:: Users should not create a guard condition with this constructor, instead they 38 | should call :meth:`.Node.create_guard_condition`. 39 | """ 40 | self._context = get_default_context() if context is None else context 41 | 42 | if self._context.handle is None: 43 | raise RuntimeError('Context must be initialized a GuardCondition can be created') 44 | 45 | with self._context.handle: 46 | self.__gc = _rclpy.GuardCondition(self._context.handle) 47 | self.callback = callback 48 | self.callback_group = callback_group 49 | # True when the callback is ready to fire but has not been "taken" by an executor 50 | self._executor_event = False 51 | # True when the executor sees this has been triggered but has not yet been handled 52 | self._executor_triggered = False 53 | 54 | def trigger(self) -> None: 55 | with self.__gc: 56 | self.__gc.trigger_guard_condition() 57 | 58 | @property 59 | def handle(self) -> _rclpy.GuardCondition: 60 | return self.__gc 61 | 62 | def destroy(self) -> None: 63 | """ 64 | Destroy a container for a ROS guard condition. 65 | 66 | .. warning:: Users should not destroy a guard condition with this method, instead 67 | they should call :meth:`.Node.destroy_guard_condition`. 68 | """ 69 | self.handle.destroy_when_not_in_use() 70 | -------------------------------------------------------------------------------- /rclpy/test/test_wait_for_message.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Sony Group Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import threading 16 | import time 17 | from typing import TYPE_CHECKING 18 | import unittest 19 | 20 | import rclpy 21 | import rclpy.context 22 | from rclpy.qos import QoSProfile 23 | from rclpy.wait_for_message import wait_for_message 24 | from test_msgs.msg import BasicTypes 25 | 26 | MSG_DATA = 100 27 | TOPIC_NAME = 'wait_for_message_topic' 28 | 29 | 30 | class TestWaitForMessage(unittest.TestCase): 31 | 32 | if TYPE_CHECKING: 33 | context: rclpy.context.Context 34 | node: rclpy.node.Node 35 | 36 | @classmethod 37 | def setUpClass(cls) -> None: 38 | cls.context = rclpy.context.Context() 39 | rclpy.init(context=cls.context) 40 | cls.node = rclpy.create_node('publisher_node', context=cls.context) 41 | 42 | @classmethod 43 | def tearDownClass(cls) -> None: 44 | cls.node.destroy_node() 45 | rclpy.shutdown(context=cls.context) 46 | 47 | def _publish_message(self) -> None: 48 | pub = self.node.create_publisher(BasicTypes, TOPIC_NAME, 1) 49 | msg = BasicTypes() 50 | msg.int32_value = MSG_DATA 51 | while True: 52 | if self.node.count_subscribers(TOPIC_NAME) > 0: 53 | pub.publish(msg) 54 | break 55 | time.sleep(1) 56 | pub.destroy() 57 | 58 | def test_wait_for_message(self) -> None: 59 | t = threading.Thread(target=self._publish_message) 60 | t.start() 61 | ret, msg = wait_for_message(BasicTypes, self.node, TOPIC_NAME, qos_profile=1) 62 | self.assertTrue(ret) 63 | self.assertEqual(msg.int32_value, MSG_DATA) # type: ignore[union-attr] 64 | t.join() 65 | 66 | def test_wait_for_message_qos(self) -> None: 67 | t = threading.Thread(target=self._publish_message) 68 | t.start() 69 | ret, msg = wait_for_message( 70 | BasicTypes, self.node, TOPIC_NAME, qos_profile=QoSProfile(depth=1)) 71 | self.assertTrue(ret) 72 | self.assertEqual(msg.int32_value, MSG_DATA) # type: ignore[union-attr] 73 | t.join() 74 | 75 | def test_wait_for_message_timeout(self) -> None: 76 | ret, _ = wait_for_message(BasicTypes, self.node, TOPIC_NAME, time_to_wait=1) 77 | self.assertFalse(ret) 78 | 79 | 80 | if __name__ == '__main__': 81 | unittest.main() 82 | -------------------------------------------------------------------------------- /rclpy/test/test_type_description_service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | import rclpy 18 | from rclpy.client import Client 19 | import rclpy.context 20 | from rclpy.executors import SingleThreadedExecutor 21 | from rclpy.qos import qos_profile_services_default 22 | from test_msgs.msg import BasicTypes 23 | from type_description_interfaces.srv import GetTypeDescription 24 | 25 | 26 | class TestTypeDescriptionService(unittest.TestCase): 27 | 28 | def setUp(self) -> None: 29 | self.context = rclpy.context.Context() 30 | rclpy.init(context=self.context) 31 | 32 | self.test_node = rclpy.create_node( 33 | 'test_type_description_service', 34 | namespace='/rclpy', 35 | context=self.context) 36 | self.test_topic = '/rclpy/basic_types' 37 | self.test_pub = self.test_node.create_publisher( 38 | BasicTypes, self.test_topic, 10) 39 | 40 | self.get_type_description_client: Client[GetTypeDescription.Request, 41 | GetTypeDescription.Response] = \ 42 | self.test_node.create_client( 43 | GetTypeDescription, '/rclpy/test_parameter_service/get_type_description', 44 | qos_profile=qos_profile_services_default) 45 | 46 | self.executor = SingleThreadedExecutor(context=self.context) 47 | self.executor.add_node(self.test_node) 48 | 49 | def tearDown(self) -> None: 50 | self.executor.shutdown() 51 | self.test_node.destroy_node() 52 | rclpy.shutdown(context=self.context) 53 | 54 | def test_get_type_description(self) -> None: 55 | pub_infos = self.test_node.get_publishers_info_by_topic(self.test_topic) 56 | assert len(pub_infos) 57 | type_hash = pub_infos[0].topic_type_hash 58 | 59 | request = GetTypeDescription.Request( 60 | type_name='test_msgs/msg/BasicTypes', 61 | type_hash=type_hash, 62 | include_type_sources=True) 63 | future = self.get_type_description_client.call_async(request) 64 | self.executor.spin_until_future_complete(future) 65 | response = future.result() 66 | assert response is not None 67 | assert response.successful 68 | assert response.type_description.type_description.type_name == 'test_msgs/msg/BasicTypes' 69 | assert len(response.type_sources) 70 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/guard_condition.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include "context.hpp" 24 | #include "exceptions.hpp" 25 | #include "guard_condition.hpp" 26 | 27 | namespace rclpy 28 | { 29 | GuardCondition::GuardCondition(Context & context) 30 | : context_(context) 31 | { 32 | rcl_guard_condition_ = std::shared_ptr( 33 | new rcl_guard_condition_t, 34 | [](rcl_guard_condition_t * guard_condition) 35 | { 36 | rcl_ret_t ret = rcl_guard_condition_fini(guard_condition); 37 | if (RCL_RET_OK != ret) { 38 | // Warning should use line number of the current stack frame 39 | int stack_level = 1; 40 | PyErr_WarnFormat( 41 | PyExc_RuntimeWarning, stack_level, "Failed to fini guard condition: %s", 42 | rcl_get_error_string().str); 43 | rcl_reset_error(); 44 | } 45 | delete guard_condition; 46 | }); 47 | 48 | *rcl_guard_condition_ = rcl_get_zero_initialized_guard_condition(); 49 | rcl_guard_condition_options_t gc_options = rcl_guard_condition_get_default_options(); 50 | 51 | rcl_ret_t ret = rcl_guard_condition_init( 52 | rcl_guard_condition_.get(), context.rcl_ptr(), gc_options); 53 | if (RCL_RET_OK != ret) { 54 | throw RCLError("failed to create guard_condition"); 55 | } 56 | } 57 | 58 | void 59 | GuardCondition::destroy() 60 | { 61 | rcl_guard_condition_.reset(); 62 | context_.destroy(); 63 | } 64 | 65 | void 66 | GuardCondition::trigger_guard_condition() 67 | { 68 | rcl_ret_t ret = rcl_trigger_guard_condition(rcl_guard_condition_.get()); 69 | 70 | if (RCL_RET_OK != ret) { 71 | throw RCLError("failed to trigger guard condition"); 72 | } 73 | } 74 | 75 | void define_guard_condition(py::object module) 76 | { 77 | py::class_>(module, "GuardCondition") 78 | .def(py::init()) 79 | .def_property_readonly( 80 | "pointer", [](const GuardCondition & guard_condition) { 81 | return reinterpret_cast(guard_condition.rcl_ptr()); 82 | }, 83 | "Get the address of the entity as an integer") 84 | .def( 85 | "trigger_guard_condition", &GuardCondition::trigger_guard_condition, 86 | "Trigger a general purpose guard condition"); 87 | } 88 | } // namespace rclpy 89 | -------------------------------------------------------------------------------- /rclpy/rclpy/wait_for_message.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Sony Group Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Literal, Tuple, Type, Union 16 | 17 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 18 | from rclpy.node import Node 19 | from rclpy.qos import QoSProfile 20 | from rclpy.signals import SignalHandlerGuardCondition 21 | from rclpy.type_support import MsgT 22 | from rclpy.utilities import timeout_sec_to_nsec 23 | 24 | 25 | def wait_for_message( 26 | msg_type: Type[MsgT], 27 | node: 'Node', 28 | topic: str, 29 | *, 30 | qos_profile: Union[QoSProfile, int] = 1, 31 | time_to_wait: Union[int, float] = -1 32 | ) -> Union[Tuple[Literal[True], MsgT], Tuple[Literal[False], None]]: 33 | """ 34 | Wait for the next incoming message. 35 | 36 | :param msg_type: message type 37 | :param node: node to initialize the subscription on 38 | :param topic: topic name to wait for message 39 | :param qos_profile: QoS profile to use for the subscription 40 | :param time_to_wait: seconds to wait before returning 41 | :returns: (True, msg) if a message was successfully received, (False, None) if message 42 | could not be obtained or shutdown was triggered asynchronously on the context. 43 | """ 44 | context = node.context 45 | 46 | if context.handle is None: 47 | raise RuntimeError('Cannot create Waitset without a context.handle') 48 | 49 | wait_set = _rclpy.WaitSet(1, 1, 0, 0, 0, 0, context.handle) 50 | wait_set.clear_entities() 51 | 52 | sub = node.create_subscription(msg_type, topic, lambda _: None, qos_profile=qos_profile) 53 | try: 54 | wait_set.add_subscription(sub.handle) 55 | sigint_gc = SignalHandlerGuardCondition(context=context) 56 | wait_set.add_guard_condition(sigint_gc.handle) 57 | 58 | timeout_nsec = timeout_sec_to_nsec(time_to_wait) 59 | wait_set.wait(timeout_nsec) 60 | 61 | subs_ready = wait_set.get_ready_entities('subscription') 62 | guards_ready = wait_set.get_ready_entities('guard_condition') 63 | 64 | if guards_ready: 65 | if sigint_gc.handle.pointer in guards_ready: 66 | return False, None 67 | 68 | if subs_ready: 69 | if sub.handle.pointer in subs_ready: 70 | msg_info = sub.handle.take_message(sub.msg_type, sub.raw) 71 | if msg_info is not None: 72 | return True, msg_info[0] 73 | finally: 74 | node.destroy_subscription(sub) 75 | 76 | return False, None 77 | -------------------------------------------------------------------------------- /rclpy/rclpy/logging_service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Sony Group Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TYPE_CHECKING 16 | 17 | from rcl_interfaces.msg import LoggerLevel, SetLoggerLevelsResult 18 | from rcl_interfaces.srv import GetLoggerLevels 19 | from rcl_interfaces.srv import SetLoggerLevels 20 | import rclpy 21 | from rclpy.impl.logging_severity import LoggingSeverity 22 | from rclpy.qos import qos_profile_services_default 23 | from rclpy.validate_topic_name import TOPIC_SEPARATOR_STRING 24 | 25 | if TYPE_CHECKING: 26 | from rclpy.node import Node 27 | 28 | 29 | class LoggingService: 30 | 31 | def __init__(self, node: 'Node'): 32 | node_name = node.get_name() 33 | 34 | get_logger_name_service_name = \ 35 | TOPIC_SEPARATOR_STRING.join((node_name, 'get_logger_levels')) 36 | node.create_service( 37 | GetLoggerLevels, get_logger_name_service_name, 38 | self._get_logger_levels, qos_profile=qos_profile_services_default 39 | ) 40 | 41 | set_logger_name_service_name = \ 42 | TOPIC_SEPARATOR_STRING.join((node_name, 'set_logger_levels')) 43 | node.create_service( 44 | SetLoggerLevels, set_logger_name_service_name, 45 | self._set_logger_levels, qos_profile=qos_profile_services_default 46 | ) 47 | 48 | def _get_logger_levels(self, request: GetLoggerLevels.Request, 49 | response: GetLoggerLevels.Response) -> GetLoggerLevels.Response: 50 | for name in request.names: 51 | logger_level = LoggerLevel() 52 | logger_level.name = name 53 | try: 54 | ret_level = rclpy.logging.get_logger_level(name) 55 | except RuntimeError: 56 | ret_level = LoggingSeverity.UNSET 57 | logger_level.level = ret_level 58 | response.levels.append(logger_level) 59 | return response 60 | 61 | def _set_logger_levels(self, request: SetLoggerLevels.Request, 62 | response: SetLoggerLevels.Response) -> SetLoggerLevels.Response: 63 | for level in request.levels: 64 | result = SetLoggerLevelsResult() 65 | result.successful = False 66 | try: 67 | rclpy.logging.set_logger_level(level.name, level.level, detailed_error=True) 68 | result.successful = True 69 | except ValueError: 70 | result.reason = 'Failed reason: Invalid logger level.' 71 | except RuntimeError as e: 72 | result.reason = str(e) 73 | response.results.append(result) 74 | return response 75 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/rcl_support.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 Brad Martin 2 | // Copyright 2024 Merlin Labs, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | #include "events_executor/rcl_support.hpp" 16 | 17 | #include 18 | 19 | namespace py = pybind11; 20 | 21 | namespace rclpy 22 | { 23 | namespace events_executor 24 | { 25 | 26 | extern "C" void RclEventCallbackTrampoline(const void * user_data, size_t number_of_events) 27 | { 28 | const auto cb = reinterpret_cast *>(user_data); 29 | try { 30 | (*cb)(number_of_events); 31 | } catch (const std::exception & e) { 32 | // Catch and print any exception to avoid propagation to c code 33 | std::fprintf(stderr, "%s\n", e.what()); 34 | std::terminate(); 35 | } 36 | } 37 | 38 | RclCallbackManager::RclCallbackManager(EventsQueue * events_queue) 39 | : events_queue_(events_queue) {} 40 | 41 | RclCallbackManager::~RclCallbackManager() 42 | { 43 | // Should not still have any callbacks registered when we exit, because otherwise RCL can call 44 | // pointers that will no longer be valid. We can't throw an exception here, but we can explode. 45 | if (!owned_cbs_.empty()) { 46 | py::gil_scoped_acquire gil_acquire; 47 | py::print("Destroying callback manager with callbacks remaining"); 48 | ::abort(); 49 | } 50 | } 51 | 52 | const void * RclCallbackManager::MakeCallback( 53 | const void * key, std::function callback, std::shared_ptr with) 54 | { 55 | // We don't support replacing an existing callback with a new one, because it gets tricky making 56 | // sure we don't delete an old callback while the middleware still holds a pointer to it. 57 | if (owned_cbs_.count(key) != 0) { 58 | throw py::key_error("Attempt to replace existing callback"); 59 | } 60 | CbEntry new_entry; 61 | new_entry.cb = 62 | std::make_unique>([this, callback, key](size_t number_of_events) { 63 | events_queue_->Enqueue([this, callback, key, number_of_events]() { 64 | if (!owned_cbs_.count(key)) { 65 | // This callback has been removed, just drop it as the objects it may want to touch may 66 | // no longer exist. 67 | return; 68 | } 69 | callback(number_of_events); 70 | }); 71 | }); 72 | new_entry.with = with; 73 | const void * ret = new_entry.cb.get(); 74 | owned_cbs_[key] = std::move(new_entry); 75 | return ret; 76 | } 77 | 78 | void RclCallbackManager::RemoveCallback(const void * key) 79 | { 80 | if (!owned_cbs_.erase(key)) { 81 | throw py::key_error("Attempt to remove nonexistent callback"); 82 | } 83 | } 84 | 85 | } // namespace events_executor 86 | } // namespace rclpy 87 | -------------------------------------------------------------------------------- /rclpy/test/test_serialization.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import List 16 | from typing import Type 17 | 18 | import pytest 19 | 20 | from rclpy.serialization import deserialize_message 21 | from rclpy.serialization import serialize_message 22 | from rclpy.type_support import Msg 23 | 24 | from test_msgs.message_fixtures import get_test_msg 25 | from test_msgs.msg import Arrays 26 | from test_msgs.msg import BasicTypes 27 | from test_msgs.msg import BoundedSequences 28 | from test_msgs.msg import Builtins 29 | from test_msgs.msg import Constants 30 | from test_msgs.msg import Defaults 31 | from test_msgs.msg import Empty 32 | from test_msgs.msg import MultiNested 33 | from test_msgs.msg import Nested 34 | from test_msgs.msg import Strings 35 | from test_msgs.msg import UnboundedSequences 36 | from test_msgs.msg import WStrings 37 | 38 | test_msgs = [ 39 | (get_test_msg('Arrays'), Arrays), 40 | (get_test_msg('BasicTypes'), BasicTypes), 41 | (get_test_msg('BoundedSequences'), BoundedSequences), 42 | (get_test_msg('Builtins'), Builtins), 43 | (get_test_msg('Constants'), Constants), 44 | (get_test_msg('Defaults'), Defaults), 45 | (get_test_msg('Empty'), Empty), 46 | (get_test_msg('MultiNested'), MultiNested), 47 | (get_test_msg('Nested'), Nested), 48 | (get_test_msg('Strings'), Strings), 49 | (get_test_msg('UnboundedSequences'), UnboundedSequences), 50 | (get_test_msg('WStrings'), WStrings), 51 | ] 52 | 53 | 54 | @pytest.mark.parametrize('msgs,msg_type', test_msgs) 55 | def test_serialize_deserialize(msgs: List[Msg], msg_type: Type[Msg]) -> None: 56 | """Test message serialization/deserialization.""" 57 | for msg in msgs: 58 | msg_serialized = serialize_message(msg) 59 | msg_deserialized = deserialize_message(msg_serialized, msg_type) 60 | assert msg == msg_deserialized 61 | 62 | 63 | def test_set_float32() -> None: 64 | """Test message serialization/deserialization of float32 type.""" 65 | # During (de)serialization we convert to a C float before converting to a PyObject. 66 | # This can result in a loss of precision 67 | msg = BasicTypes() 68 | msg.float32_value = 1.125 # can be represented without rounding 69 | msg_serialized = serialize_message(msg) 70 | msg_deserialized = deserialize_message(msg_serialized, BasicTypes) 71 | assert msg.float32_value == msg_deserialized.float32_value 72 | 73 | msg = BasicTypes() 74 | msg.float32_value = 3.14 # can NOT be represented without rounding 75 | msg_serialized = serialize_message(msg) 76 | msg_deserialized = deserialize_message(msg_serialized, BasicTypes) 77 | assert msg.float32_value == round(msg_deserialized.float32_value, 2) 78 | -------------------------------------------------------------------------------- /rclpy/test/test_guard_condition.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TYPE_CHECKING 16 | import unittest 17 | 18 | import rclpy 19 | import rclpy.context 20 | from rclpy.executors import SingleThreadedExecutor 21 | 22 | 23 | class TestGuardCondition(unittest.TestCase): 24 | 25 | if TYPE_CHECKING: 26 | context: rclpy.context.Context 27 | node: rclpy.node.Node 28 | executor: SingleThreadedExecutor 29 | 30 | @classmethod 31 | def setUpClass(cls) -> None: 32 | cls.context = rclpy.context.Context() 33 | rclpy.init(context=cls.context) 34 | cls.node = rclpy.create_node( 35 | 'TestGuardCondition', namespace='/rclpy/test', context=cls.context) 36 | cls.executor = SingleThreadedExecutor(context=cls.context) 37 | cls.executor.add_node(cls.node) 38 | 39 | @classmethod 40 | def tearDownClass(cls) -> None: 41 | cls.executor.shutdown() 42 | cls.node.destroy_node() 43 | rclpy.shutdown(context=cls.context) 44 | 45 | def test_trigger(self) -> None: 46 | called = False 47 | 48 | def func() -> None: 49 | nonlocal called 50 | called = True 51 | 52 | gc = self.node.create_guard_condition(func) 53 | 54 | self.executor.spin_once(timeout_sec=0) 55 | self.assertFalse(called) 56 | 57 | gc.trigger() 58 | self.executor.spin_once(timeout_sec=0) 59 | self.assertTrue(called) 60 | 61 | self.node.destroy_guard_condition(gc) 62 | 63 | def test_double_trigger(self) -> None: 64 | called1 = False 65 | called2 = False 66 | 67 | def func1() -> None: 68 | nonlocal called1 69 | called1 = True 70 | 71 | def func2() -> None: 72 | nonlocal called2 73 | called2 = True 74 | 75 | gc1 = self.node.create_guard_condition(func1) 76 | gc2 = self.node.create_guard_condition(func2) 77 | 78 | self.executor.spin_once(timeout_sec=0) 79 | self.assertFalse(called1) 80 | self.assertFalse(called2) 81 | 82 | gc1.trigger() 83 | gc2.trigger() 84 | self.executor.spin_once(timeout_sec=0) 85 | self.executor.spin_once(timeout_sec=0) 86 | self.assertTrue(called1) 87 | self.assertTrue(called2) 88 | 89 | called1 = False 90 | called2 = False 91 | self.executor.spin_once(timeout_sec=0) 92 | self.assertFalse(called1) 93 | self.assertFalse(called2) 94 | 95 | self.node.destroy_guard_condition(gc1) 96 | self.node.destroy_guard_condition(gc2) 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/context.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__CONTEXT_HPP_ 16 | #define RCLPY__CONTEXT_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "destroyable.hpp" 27 | #include "exceptions.hpp" 28 | 29 | namespace py = pybind11; 30 | 31 | namespace rclpy 32 | { 33 | struct InitOptions 34 | { 35 | explicit InitOptions(rcl_allocator_t allocator) 36 | { 37 | rcl_options = rcl_get_zero_initialized_init_options(); 38 | rcl_ret_t ret = rcl_init_options_init(&rcl_options, allocator); 39 | if (RCL_RET_OK != ret) { 40 | throw RCLError("Failed to initialize init options"); 41 | } 42 | } 43 | 44 | ~InitOptions() 45 | { 46 | rcl_ret_t ret = rcl_init_options_fini(&rcl_options); 47 | if (RCL_RET_OK != ret) { 48 | int stack_level = 1; 49 | PyErr_WarnFormat( 50 | PyExc_RuntimeWarning, stack_level, 51 | "[rclpy| %s : %s ]: failed to fini rcl_init_options_t in destructor: %s", 52 | RCUTILS_STRINGIFY(__FILE__), RCUTILS_STRINGIFY(__LINE__), rcl_get_error_string().str); 53 | rcl_reset_error(); 54 | } 55 | } 56 | 57 | rcl_init_options_t rcl_options; 58 | }; 59 | 60 | void shutdown_contexts(); 61 | 62 | class Context : public Destroyable, public std::enable_shared_from_this 63 | { 64 | public: 65 | /// Create a Context instance. 66 | /** 67 | * Raises MemoryError if allocating memory fails. 68 | * Raises RuntimeError if creating the context fails. 69 | * 70 | * \param[in] pyargs List of command line arguments 71 | * \param[in] domain_id domain id to be set in this context 72 | */ 73 | Context(py::list pyargs, size_t domain_id); 74 | 75 | /// Retrieves domain id from init_options of context 76 | /** 77 | * \param[in] pycontext Capsule containing rcl_context_t 78 | * \return domain id 79 | */ 80 | size_t 81 | get_domain_id(); 82 | 83 | /// Status of the the client library 84 | /** 85 | * \return True if rcl is running properly, False otherwise 86 | */ 87 | bool 88 | ok(); 89 | 90 | void 91 | shutdown(); 92 | 93 | /// Get rcl_context_t pointer 94 | rcl_context_t * rcl_ptr() const 95 | { 96 | return rcl_context_.get(); 97 | } 98 | 99 | /// Force an early destruction of this object 100 | void destroy() override; 101 | 102 | private: 103 | std::shared_ptr rcl_context_; 104 | bool already_shutdown_{false}; 105 | }; 106 | 107 | /// Define a pybind11 wrapper for an rclpy::Context 108 | void define_context(py::object module); 109 | } // namespace rclpy 110 | 111 | #endif // RCLPY__CONTEXT_HPP_ 112 | -------------------------------------------------------------------------------- /rclpy/test/test_logging_rosout.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import time 16 | 17 | import pytest 18 | 19 | from rcl_interfaces.msg import Log 20 | import rclpy 21 | from rclpy.executors import SingleThreadedExecutor 22 | from rclpy.logging import LoggingSeverity 23 | 24 | TEST_PARAMETERS = [ 25 | # name, enable_global_rosout_logs, enable_node_rosout, expected_data 26 | ('enable_global_rosout_enable_node_rosout', True, True, True), 27 | ('enable_global_rosout_disable_node_rosout', True, False, False), 28 | ('disable_global_rosout_enable_node_rosout', False, True, False), 29 | ('disable_global_rosout_disable_node_rosout', False, False, False), 30 | ] 31 | 32 | rosout_subscription_msg = None # None=No result yet 33 | 34 | 35 | def rosout_subscription_callback(msg: Log) -> None: 36 | global rosout_subscription_msg 37 | rosout_subscription_msg = msg 38 | 39 | 40 | @pytest.mark.parametrize( 41 | 'name,enable_global_rosout_logs,enable_node_rosout,expected_data', 42 | TEST_PARAMETERS) 43 | def test_enable_rosout( 44 | name: str, 45 | enable_global_rosout_logs: bool, 46 | enable_node_rosout: bool, 47 | expected_data: bool 48 | ) -> None: 49 | if enable_global_rosout_logs: 50 | args = ['--ros-args', '--enable-rosout-logs'] 51 | else: 52 | args = ['--ros-args', '--disable-rosout-logs'] 53 | 54 | context = rclpy.context.Context() 55 | rclpy.init(context=context, args=args) 56 | executor = SingleThreadedExecutor(context=context) 57 | 58 | # create node 59 | node = rclpy.create_node( 60 | node_name='my_node_'+name, 61 | namespace='/my_ns', 62 | enable_rosout=enable_node_rosout, 63 | context=context 64 | ) 65 | executor.add_node(node) 66 | 67 | global rosout_subscription_msg 68 | rosout_subscription_msg = None 69 | # create subscriber of 'rosout' topic 70 | node.create_subscription( 71 | Log, 72 | '/rosout', 73 | rosout_subscription_callback, 74 | 1 75 | ) 76 | 77 | max_difference_time = 5 78 | begin_time = time.time() 79 | message_data = 'SOMETHING' 80 | while rosout_subscription_msg is None and int(time.time() - begin_time) <= max_difference_time: 81 | node.get_logger().info(message_data) 82 | executor.spin_once(timeout_sec=1) 83 | 84 | if expected_data: 85 | assert (rosout_subscription_msg is not None) 86 | assert (isinstance(rosout_subscription_msg, Log)) 87 | assert (LoggingSeverity(rosout_subscription_msg.level) == LoggingSeverity.INFO) 88 | assert (len(rosout_subscription_msg.msg) != 0) 89 | assert (rosout_subscription_msg.msg == message_data) 90 | else: 91 | assert (rosout_subscription_msg is None) 92 | 93 | node.destroy_node() 94 | rclpy.shutdown(context=context) 95 | -------------------------------------------------------------------------------- /rclpy/rosdoc2.yaml: -------------------------------------------------------------------------------- 1 | ## This 'attic section' self-documents this file's type and version. 2 | type: 'rosdoc2 config' 3 | version: 1 4 | 5 | --- 6 | 7 | settings: 8 | ## If this is true, a standard index page is generated in the output directory. 9 | ## It uses the package information from the 'package.xml' to show details 10 | ## about the package, creates a table of contents for the various builders 11 | ## that were run, and may contain links to things like build farm jobs for 12 | ## this package or links to other versions of this package. 13 | ## If this is not specified explicitly, it defaults to 'true'. 14 | generate_package_index: true 15 | 16 | ## This setting is relevant mostly if the standard Python package layout cannot 17 | ## be assumed for 'sphinx-apidoc' invocation. The user can provide the path 18 | ## (relative to the 'package.xml' file) where the Python modules defined by this 19 | ## package are located. 20 | python_source: 'rclpy' 21 | 22 | ## This setting, if true, attempts to run `doxygen` and the `breathe`/`exhale` 23 | ## extensions to `sphinx` regardless of build type. This is most useful if the 24 | ## user would like to generate C/C++ API documentation for a package that is not 25 | ## of the `ament_cmake/cmake` build type. 26 | always_run_doxygen: false 27 | 28 | ## This setting, if true, attempts to run `sphinx-apidoc` regardless of build 29 | ## type. This is most useful if the user would like to generate Python API 30 | ## documentation for a package that is not of the `ament_python` build type. 31 | always_run_sphinx_apidoc: true 32 | 33 | # This setting, if True, will ensure breathe is part of the 'extensions', 34 | # and will set all of the breathe configurations, if not set, and override 35 | # settings as needed if they are set by this configuration. 36 | enable_breathe: false 37 | 38 | # This setting, if True, will ensure exhale is part of the 'extensions', 39 | # and will set all of the exhale configurations, if not set, and override 40 | # settings as needed if they are set by this configuration. 41 | enable_exhale: false 42 | 43 | # This setting, if provided, will override the build_type of this package 44 | # for documentation purposes only. If not provided, documentation will be 45 | # generated assuming the build_type in package.xml. 46 | override_build_type: 'ament_python' 47 | 48 | builders: 49 | ## Each stanza represents a separate build step, performed by a specific 'builder'. 50 | ## The key of each stanza is the builder to use; this must be one of the 51 | ## available builders. 52 | ## The value of each stanza is a dictionary of settings for the builder that 53 | ## outputs to that directory. 54 | ## Required keys in the settings dictionary are: 55 | ## * 'output_dir' - determines output subdirectory for builder instance 56 | ## relative to --output-directory 57 | ## * 'name' - used when referencing the built docs from the index. 58 | 59 | # - doxygen: { 60 | # name: 'rclpy Public C/C++ API', 61 | # output_dir: 'generated/doxygen' 62 | # } 63 | - sphinx: { 64 | name: 'rclpy', 65 | ## This path is relative to output staging. 66 | # doxygen_xml_directory: 'generated/doxygen/xml', 67 | # todo: Provide user sphinx_sourcedir below after warnings have been addressed. 68 | sphinx_sourcedir: 'docs/source/', 69 | output_dir: '' 70 | } 71 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/timers_manager.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 Brad Martin 2 | // Copyright 2024 Merlin Labs, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef RCLPY__EVENTS_EXECUTOR__TIMERS_MANAGER_HPP_ 17 | #define RCLPY__EVENTS_EXECUTOR__TIMERS_MANAGER_HPP_ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "events_executor/events_queue.hpp" 29 | #include "events_executor/python_eq_handler.hpp" 30 | #include "events_executor/python_hasher.hpp" 31 | #include "events_executor/scoped_with.hpp" 32 | 33 | namespace rclpy 34 | { 35 | namespace events_executor 36 | { 37 | 38 | /// This class manages low-level rcl timers in the system on behalf of EventsExecutor. 39 | class RclTimersManager 40 | { 41 | public: 42 | /// The given pointer is aliased and must live for the lifetime of this object. 43 | explicit RclTimersManager(EventsQueue *); 44 | ~RclTimersManager(); 45 | 46 | void AddTimer(rcl_timer_t *, std::function ready_callback); 47 | void RemoveTimer(rcl_timer_t *); 48 | 49 | private: 50 | EventsQueue * const events_queue_; 51 | 52 | class ClockManager; 53 | /// Handlers for each distinct clock source in the system. 54 | std::unordered_map> clock_managers_; 55 | }; 56 | 57 | /// This class manages rclpy.Timer Python objects on behalf of EventsExecutor. 58 | class TimersManager 59 | { 60 | public: 61 | /// @param events_queue is aliased and must live for the lifetime of this object. 62 | /// @param timer_ready_callback will be invoked with the timer handle and info whenever a managed 63 | /// timer is ready for servicing. 64 | TimersManager( 65 | EventsQueue * events_queue, 66 | std::function timer_ready_callback); 67 | ~TimersManager(); 68 | 69 | /// Accessor for underlying rcl timer manager, for management of non-Python timers. 70 | RclTimersManager & rcl_manager() {return rcl_manager_;} 71 | 72 | // Both of these methods expect the GIL to be held when they are called. 73 | void AddTimer(pybind11::handle timer); 74 | void RemoveTimer(pybind11::handle timer); 75 | 76 | private: 77 | struct PyRclMapping 78 | { 79 | /// Marks the corresponding Python object as in-use for as long as we're using the rcl pointer 80 | /// derived from it. 81 | std::unique_ptr with; 82 | 83 | /// The underlying rcl object 84 | rcl_timer_t * rcl_ptr{}; 85 | }; 86 | 87 | RclTimersManager rcl_manager_; 88 | const std::function ready_callback_; 89 | 90 | std::unordered_map timer_mappings_; 91 | }; 92 | 93 | } // namespace events_executor 94 | } // namespace rclpy 95 | 96 | #endif // RCLPY__EVENTS_EXECUTOR__TIMERS_MANAGER_HPP_ 97 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/events_executor/rcl_support.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024-2025 Brad Martin 2 | // Copyright 2024 Merlin Labs, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #ifndef RCLPY__EVENTS_EXECUTOR__RCL_SUPPORT_HPP_ 17 | #define RCLPY__EVENTS_EXECUTOR__RCL_SUPPORT_HPP_ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "events_executor/events_queue.hpp" 25 | #include "events_executor/scoped_with.hpp" 26 | 27 | namespace rclpy 28 | { 29 | namespace events_executor 30 | { 31 | 32 | /// Use this for all RCL event callbacks. Use the return value from 33 | /// RclCallbackManager::MakeCallback() as the user data arg. 34 | /// 35 | /// Note that RCL callbacks are invoked in some arbitrary thread originating from the middleware. 36 | /// Callbacks should process quickly to avoid blocking the middleware; i.e. all actual work should 37 | /// be posted to an EventsQueue running in another thread. 38 | extern "C" void RclEventCallbackTrampoline(const void * user_data, size_t number_of_events); 39 | 40 | /// Creates and maintains callback wrappers used with the RCL C library. 41 | class RclCallbackManager 42 | { 43 | public: 44 | /// All user callbacks will be posted on the @p events_queue given to the constructor. This 45 | /// pointer is aliased and must live for the lifetime of this object. These callbacks will be 46 | /// invoked without the Python Global Interpreter Lock held, so if they need to access Python at 47 | /// all make sure to acquire that explicitly. 48 | explicit RclCallbackManager(EventsQueue * events_queue); 49 | ~RclCallbackManager(); 50 | 51 | /// Creates a callback wrapper to be passed to RCL C functions. @p key should be a pointer to 52 | /// the rcl object that will be associated with the callback. @p with protects the _rclpy object 53 | /// handle owning the RCL object, for the duration the callback is established. 54 | const void * MakeCallback( 55 | const void * key, std::function callback, std::shared_ptr with); 56 | 57 | /// Discard a previously constructed callback. @p key should be the same value provided to 58 | /// MakeCallback(). Caution: ensure that RCL is no longer using a callback before invoking this. 59 | void RemoveCallback(const void * key); 60 | 61 | private: 62 | /// The C RCL interface deals in raw pointers, so someone needs to own the C++ function objects 63 | /// we'll be calling into. We use unique pointers so the raw pointer to the object remains 64 | /// stable while the map is manipulated. 65 | struct CbEntry 66 | { 67 | std::unique_ptr> cb; 68 | std::shared_ptr with; 69 | }; 70 | 71 | EventsQueue * const events_queue_; 72 | 73 | /// The map key is the raw pointer to the RCL entity object (subscription, etc) associated with 74 | /// the callback. 75 | std::unordered_map owned_cbs_; 76 | }; 77 | 78 | } // namespace events_executor 79 | } // namespace rclpy 80 | 81 | #endif // RCLPY__EVENTS_EXECUTOR__RCL_SUPPORT_HPP_ 82 | -------------------------------------------------------------------------------- /rclpy/test/test_parameter_service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Sony Group Corporation. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from rcl_interfaces.msg import ParameterType 18 | from rcl_interfaces.srv import DescribeParameters 19 | from rcl_interfaces.srv import GetParameters 20 | import rclpy 21 | from rclpy.client import Client 22 | import rclpy.context 23 | from rclpy.executors import SingleThreadedExecutor 24 | from rclpy.parameter import Parameter 25 | from rclpy.qos import qos_profile_services_default 26 | 27 | 28 | class TestParameterService(unittest.TestCase): 29 | 30 | def setUp(self) -> None: 31 | self.context = rclpy.context.Context() 32 | rclpy.init(context=self.context) 33 | self.test_node = rclpy.create_node( 34 | 'test_parameter_service', 35 | namespace='/rclpy', 36 | context=self.context) 37 | 38 | self.get_parameter_client: Client[GetParameters.Request, 39 | GetParameters.Response] = self.test_node.create_client( 40 | GetParameters, '/rclpy/test_parameter_service/get_parameters', 41 | qos_profile=qos_profile_services_default 42 | ) 43 | 44 | self.describe_parameters_client: Client[DescribeParameters.Response, 45 | DescribeParameters.Response] = \ 46 | self.test_node.create_client( 47 | DescribeParameters, 48 | '/rclpy/test_parameter_service/describe_parameters', 49 | qos_profile=qos_profile_services_default) 50 | 51 | self.executor = SingleThreadedExecutor(context=self.context) 52 | self.executor.add_node(self.test_node) 53 | 54 | def tearDown(self) -> None: 55 | self.executor.shutdown() 56 | self.test_node.destroy_node() 57 | rclpy.shutdown(context=self.context) 58 | 59 | def test_get_uninitialized_parameter(self) -> None: 60 | self.test_node.declare_parameter('uninitialized_parameter', Parameter.Type.STRING) 61 | 62 | # The type in description should be STRING 63 | request = DescribeParameters.Request() 64 | request.names = ['uninitialized_parameter'] 65 | future = self.describe_parameters_client.call_async(request) 66 | self.executor.spin_until_future_complete(future) 67 | results = future.result() 68 | assert results is not None 69 | assert len(results.descriptors) == 1 70 | assert results.descriptors[0].type == ParameterType.PARAMETER_STRING 71 | assert results.descriptors[0].name == 'uninitialized_parameter' 72 | 73 | # The value should be empty 74 | request = GetParameters.Request() 75 | request.names = ['uninitialized_parameter'] 76 | future = self.get_parameter_client.call_async(request) 77 | self.executor.spin_until_future_complete(future) 78 | results = future.result() 79 | assert results is not None 80 | assert results.values == [] 81 | 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /rclpy/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rclpy 5 | 10.0.3 6 | Package containing the Python client. 7 | 8 | Shane Loretz 9 | Aditya Pande 10 | 11 | Apache License 2.0 12 | 13 | Esteve Fernandez 14 | Ivan Paunovic 15 | Jacob Perron 16 | William Woodall 17 | 18 | ament_cmake 19 | 20 | lifecycle_msgs 21 | rcl 22 | rcl_action 23 | rcl_interfaces 24 | rcl_lifecycle 25 | rcl_logging_interface 26 | rcl_yaml_param_parser 27 | pybind11-dev 28 | python3-dev 29 | rcpputils 30 | rcutils 31 | rmw 32 | rmw_implementation 33 | rmw_implementation_cmake 34 | rosidl_runtime_c 35 | type_description_interfaces 36 | 37 | action_msgs 38 | ament_index_python 39 | builtin_interfaces 40 | lifecycle_msgs 41 | python3-typing-extensions 42 | python3-yaml 43 | rcl 44 | rcl_action 45 | rcl_interfaces 46 | rcl_lifecycle 47 | rcl_logging_interface 48 | rcl_yaml_param_parser 49 | rcpputils 50 | rcutils 51 | rmw 52 | rmw_implementation 53 | rosgraph_msgs 54 | rosidl_runtime_c 55 | rpyutils 56 | service_msgs 57 | type_description_interfaces 58 | unique_identifier_msgs 59 | 60 | ament_cmake_gtest 61 | ament_cmake_pytest 62 | ament_lint_auto 63 | ament_lint_common 64 | python3-pytest 65 | rosidl_generator_py 66 | test_msgs 67 | 68 | python3-sphinx 69 | python3-sphinx-rtd-theme 70 | 74 | rclpy.duration 75 | rclpy.qos 76 | rclpy.clock 77 | 78 | 79 | ament_cmake 80 | rosdoc2.yaml 81 | 82 | 83 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/event_handle.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__EVENT_HANDLE_HPP_ 16 | #define RCLPY__EVENT_HANDLE_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include "destroyable.hpp" 26 | #include "publisher.hpp" 27 | #include "subscription.hpp" 28 | 29 | namespace py = pybind11; 30 | 31 | namespace rclpy 32 | { 33 | /* 34 | * This class will create an event handle for the given subscription. 35 | */ 36 | class EventHandle : public Destroyable, public std::enable_shared_from_this 37 | { 38 | public: 39 | /// Create a subscription event 40 | /** 41 | * Raises UnsupportedEventTypeError if the event type is not supported 42 | * Raises TypeError if arguments are not of the correct types i.e. a subscription capsule 43 | * Raises MemoryError if the event can't be allocated 44 | * Raises RCLError if event initialization failed in rcl 45 | * 46 | * \param[in] subscription Subscription wrapping the underlying ``rcl_subscription_t`` object. 47 | * \param[in] event_type Type of event to create 48 | */ 49 | EventHandle(rclpy::Subscription & subscriber, rcl_subscription_event_type_t event_type); 50 | 51 | /// Create a publisher event 52 | /** 53 | * This function will create an event handle for the given publisher. 54 | * 55 | * Raises UnsupportedEventTypeError if the event type is not supported 56 | * Raises TypeError if arguments are not of the correct types i.e. a publisher capsule 57 | * Raises MemoryError if the event can't be allocated 58 | * Raises RCLError if event initialization failed in rcl 59 | * 60 | * \param[in] publisher Publisher wrapping the underlying ``rcl_publisher_t`` object. 61 | * \param[in] event_type Type of event to create 62 | */ 63 | EventHandle(rclpy::Publisher & publisher, rcl_publisher_event_type_t event_type); 64 | 65 | ~EventHandle() = default; 66 | 67 | /// Get pending data from a ready QoS event. 68 | /** 69 | * After having determined that a middleware event is ready, get the callback payload. 70 | * 71 | * Raises MemoryError if event data can't be allocated 72 | * Raises TypeError if arguments are not of the correct types 73 | * Raises RCLError if taking event data failed in rcl 74 | * 75 | * \return Event data as an instance of a suitable rclpy.event_handle type, or None 76 | * if no event was taken. 77 | */ 78 | py::object 79 | take_event(); 80 | 81 | /// Get rcl_event_t pointer 82 | rcl_event_t * 83 | rcl_ptr() const 84 | { 85 | return rcl_event_.get(); 86 | } 87 | 88 | /// Force an early destruction of this object 89 | void 90 | destroy() override; 91 | 92 | private: 93 | std::variant event_type_; 94 | std::variant grandparent_; 95 | std::shared_ptr rcl_event_; 96 | }; 97 | 98 | /// Define a pybind11 wrapper for an rclpy::EventHandle 99 | /** 100 | * \param[in] module a pybind11 module to add the definition to 101 | */ 102 | void 103 | define_event_handle(py::module module); 104 | } // namespace rclpy 105 | 106 | #endif // RCLPY__EVENT_HANDLE_HPP_ 107 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/clock.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef RCLPY__CLOCK_HPP_ 16 | #define RCLPY__CLOCK_HPP_ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "destroyable.hpp" 25 | #include "exceptions.hpp" 26 | #include "utils.hpp" 27 | 28 | namespace py = pybind11; 29 | 30 | namespace rclpy 31 | { 32 | class Clock : public Destroyable, public std::enable_shared_from_this 33 | { 34 | public: 35 | /// Create a clock 36 | /** 37 | * Raises RCLError on initialization failure 38 | * 39 | * \param[in] clock_type enum of type ClockType 40 | * This constructor creates a Clock object 41 | */ 42 | explicit Clock(int clock_type); 43 | 44 | /// Returns the current value of the clock 45 | /** 46 | * Raises RCLError if the clock's value cannot be retrieved 47 | * 48 | * \return capsule to a time point with the current time 49 | */ 50 | rcl_time_point_t 51 | get_now(); 52 | 53 | /// Returns if a clock using ROS time has the ROS time override enabled. 54 | /** 55 | * Raises RCLError if the clock's ROS time override state cannot be retrieved 56 | * 57 | * \return true if ROS time is enabled 58 | */ 59 | bool 60 | get_ros_time_override_is_enabled(); 61 | 62 | /// Set if a clock using ROS time has the ROS time override enabled. 63 | /** 64 | * Raises RCLError if the clock's ROS time override cannot be set 65 | * 66 | * \param[in] enabled Override state to set 67 | */ 68 | void 69 | set_ros_time_override_is_enabled(bool enabled); 70 | 71 | /// Set the ROS time override for a clock using ROS time. 72 | /** 73 | * Raises RRCLError if the clock's ROS time override cannot be set 74 | * 75 | * \param[in] time_point a time point instance 76 | */ 77 | void 78 | set_ros_time_override(rcl_time_point_t time_point); 79 | 80 | /// Add a time jump callback to a clock. 81 | /** 82 | * any argument is invalid 83 | * Raises RCLError if the callback cannot be added 84 | * 85 | * \param[in] pyjump_handle Instance of rclpy.clock.JumpHandle 86 | * \param[in] on_clock_change True if callback should be called when ROS time is toggled 87 | * \param[in] min_forward minimum nanoseconds to trigger forward jump callback 88 | * \param[in] min_backward minimum negative nanoseconds to trigger backward jump callback 89 | */ 90 | void 91 | add_clock_callback( 92 | py::object pyjump_handle, 93 | bool on_clock_change, 94 | int64_t min_forward, 95 | int64_t min_backward); 96 | 97 | /// Remove a time jump callback from a clock. 98 | /** 99 | * Raises RCLError if the callback cannot be added 100 | * 101 | * \param[in] pyjump_handle Instance of rclpy.clock.JumpHandle 102 | */ 103 | void 104 | remove_clock_callback(py::object pyjump_handle); 105 | 106 | /// Get rcl_clock_t pointer 107 | rcl_clock_t * rcl_ptr() const 108 | { 109 | return rcl_clock_.get(); 110 | } 111 | 112 | /// Force an early destruction of this object 113 | void destroy() override; 114 | 115 | private: 116 | std::shared_ptr rcl_clock_; 117 | }; 118 | 119 | /// Define a pybind11 wrapper for an rclpy::Clock 120 | void define_clock(py::object module); 121 | } // namespace rclpy 122 | 123 | #endif // RCLPY__CLOCK_HPP_ 124 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/clock_event.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "clock_event.hpp" 26 | 27 | namespace py = pybind11; 28 | 29 | namespace rclpy 30 | { 31 | template 32 | void ClockEvent::wait_until(std::shared_ptr clock, rcl_time_point_t until) 33 | { 34 | // Synchronize because clock epochs might differ 35 | const rcl_time_point_t rcl_entry = clock->get_now(); 36 | const typename ClockType::time_point chrono_entry = ClockType::now(); 37 | 38 | rcl_duration_t delta_t; 39 | rcl_ret_t ret = rcl_difference_times(&rcl_entry, &until, &delta_t); 40 | 41 | if (RCL_RET_OK != ret) { 42 | throw RCLError("failed to subtract times"); 43 | } 44 | 45 | // Cast because system clock resolution is too big for nanoseconds on Windows & OSX 46 | const typename ClockType::time_point chrono_until = chrono_entry + 47 | std::chrono::duration_cast( 48 | std::chrono::nanoseconds(delta_t.nanoseconds)); 49 | 50 | // Could be a long wait, release the gil 51 | py::gil_scoped_release release; 52 | std::unique_lock lock(mutex_); 53 | cv_.wait_until(lock, chrono_until, [this]() {return state_;}); 54 | } 55 | 56 | void ClockEvent::wait_until_ros(std::shared_ptr clock, rcl_time_point_t until) 57 | { 58 | // Check if ROS time is enabled in C++ to avoid TOCTTOU with TimeSource by holding GIL 59 | if (clock->get_ros_time_override_is_enabled()) { 60 | // Could be a long wait, release the gil 61 | py::gil_scoped_release release; 62 | std::unique_lock lock(mutex_); 63 | // Caller must have setup a time jump callback to wake this event 64 | cv_.wait(lock, [this]() {return state_;}); 65 | } else { 66 | // ROS time not enabled is system time 67 | wait_until(clock, until); 68 | } 69 | } 70 | 71 | bool ClockEvent::is_set() 72 | { 73 | std::unique_lock lock(mutex_); 74 | return state_; 75 | } 76 | 77 | void ClockEvent::set() 78 | { 79 | { 80 | std::unique_lock lock(mutex_); 81 | state_ = true; 82 | } 83 | cv_.notify_all(); 84 | } 85 | 86 | void ClockEvent::clear() 87 | { 88 | { 89 | std::unique_lock lock(mutex_); 90 | state_ = false; 91 | } 92 | cv_.notify_all(); 93 | } 94 | 95 | void define_clock_event(py::object module) 96 | { 97 | py::class_(module, "ClockEvent") 98 | .def(py::init()) 99 | .def( 100 | "wait_until_steady", &ClockEvent::wait_until, 101 | "Wait for the event to be set (monotonic wait)") 102 | .def( 103 | "wait_until_system", &ClockEvent::wait_until, 104 | "Wait for the event to be set (system timed wait)") 105 | .def( 106 | "wait_until_ros", &ClockEvent::wait_until_ros, 107 | "Wait for the event to be set (ROS timed wait)") 108 | .def( 109 | "is_set", &ClockEvent::is_set, 110 | "Return True if the event is set, False otherwise.") 111 | .def( 112 | "set", &ClockEvent::set, 113 | "Set the event, waking all those who wait on it.") 114 | .def( 115 | "clear", &ClockEvent::clear, 116 | "Unset the event."); 117 | } 118 | } // namespace rclpy 119 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/action_goal_handle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "action_goal_handle.hpp" 25 | #include "action_server.hpp" 26 | #include "exceptions.hpp" 27 | #include "utils.hpp" 28 | 29 | namespace py = pybind11; 30 | 31 | namespace rclpy 32 | { 33 | ActionGoalHandle::ActionGoalHandle( 34 | rclpy::ActionServer & action_server, py::object pygoal_info_msg) 35 | : action_server_(action_server) 36 | { 37 | auto goal_info_msg = convert_from_py(pygoal_info_msg); 38 | rcl_action_goal_info_t * goal_info_msg_ptr = 39 | static_cast(goal_info_msg.get()); 40 | 41 | if (!goal_info_msg) { 42 | throw py::error_already_set(); 43 | } 44 | 45 | auto rcl_handle = rcl_action_accept_new_goal( 46 | action_server.rcl_ptr(), goal_info_msg_ptr); 47 | if (!rcl_handle) { 48 | throw rclpy::RCLError("Failed to accept new goal"); 49 | } 50 | 51 | rcl_action_goal_handle_ = std::shared_ptr( 52 | new rcl_action_goal_handle_t, 53 | [](rcl_action_goal_handle_t * goal_handle) 54 | { 55 | rcl_ret_t ret = rcl_action_goal_handle_fini(goal_handle); 56 | if (RCL_RET_OK != ret) { 57 | // Warning should use line number of the current stack frame 58 | int stack_level = 1; 59 | PyErr_WarnFormat( 60 | PyExc_RuntimeWarning, stack_level, "Error destroying action goal handle: %s", 61 | rcl_get_error_string().str); 62 | rcl_reset_error(); 63 | } 64 | delete goal_handle; 65 | }); 66 | // Copy out goal handle since action server storage disappears when it is fini'd 67 | *rcl_action_goal_handle_ = *rcl_handle; 68 | } 69 | 70 | void 71 | ActionGoalHandle::destroy() 72 | { 73 | rcl_action_goal_handle_.reset(); 74 | action_server_.destroy(); 75 | } 76 | 77 | rcl_action_goal_state_t 78 | ActionGoalHandle::get_status() 79 | { 80 | rcl_action_goal_state_t status; 81 | rcl_ret_t ret = rcl_action_goal_handle_get_status(rcl_action_goal_handle_.get(), &status); 82 | if (RCL_RET_OK != ret) { 83 | throw rclpy::RCLError("Failed to get goal status"); 84 | } 85 | 86 | return status; 87 | } 88 | 89 | void 90 | ActionGoalHandle::update_goal_state(rcl_action_goal_event_t event) 91 | { 92 | rcl_ret_t ret = rcl_action_update_goal_state(rcl_action_goal_handle_.get(), event); 93 | if (RCL_RET_OK != ret) { 94 | throw rclpy::RCLError("Failed to update goal state"); 95 | } 96 | } 97 | 98 | void 99 | define_action_goal_handle(py::module module) 100 | { 101 | py::class_>( 102 | module, "ActionGoalHandle") 103 | .def(py::init()) 104 | .def_property_readonly( 105 | "pointer", [](const ActionGoalHandle & handle) { 106 | return reinterpret_cast(handle.rcl_ptr()); 107 | }, 108 | "Get the address of the entity as an integer") 109 | .def( 110 | "get_status", &ActionGoalHandle::get_status, 111 | "Get the status of a goal.") 112 | .def( 113 | "update_goal_state", &ActionGoalHandle::update_goal_state, 114 | "Update a goal state.") 115 | .def( 116 | "is_active", &ActionGoalHandle::is_active, 117 | "Check if a goal is active."); 118 | } 119 | } // namespace rclpy 120 | -------------------------------------------------------------------------------- /rclpy/test/test_init_shutdown.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | import rclpy 17 | from rclpy import signals 18 | from rclpy.exceptions import NotInitializedException 19 | from rclpy.signals import SignalHandlerOptions 20 | 21 | 22 | def test_init() -> None: 23 | context = rclpy.context.Context() 24 | rclpy.init(context=context) 25 | assert context.ok() 26 | rclpy.shutdown(context=context) 27 | 28 | 29 | def test_init_with_unknown_ros_args() -> None: 30 | from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy 31 | 32 | context = rclpy.context.Context() 33 | unknown_ros_args_error_pattern = r'\[\'unknown\'\]' 34 | with pytest.raises(_rclpy.UnknownROSArgsError, match=unknown_ros_args_error_pattern): 35 | rclpy.init(context=context, args=['--ros-args', 'unknown']) 36 | 37 | 38 | def test_init_with_non_utf8_arguments() -> None: 39 | context = rclpy.context.Context() 40 | # Embed non decodable characters e.g. due to 41 | # wrong locale settings. 42 | # See PEP-383 for further reference. 43 | args = ['my-node.py', 'Ragnar\udcc3\udcb6k'] 44 | with pytest.raises(UnicodeEncodeError): 45 | rclpy.init(context=context, args=args) 46 | 47 | 48 | def test_init_shutdown_sequence() -> None: 49 | context = rclpy.context.Context() 50 | rclpy.init(context=context) 51 | assert context.ok() 52 | rclpy.shutdown(context=context) 53 | context = rclpy.context.Context() # context cannot be reused but should not interfere 54 | rclpy.init(context=context) 55 | assert context.ok() 56 | rclpy.shutdown(context=context) 57 | 58 | # global 59 | rclpy.init() 60 | assert rclpy.ok() 61 | rclpy.shutdown() 62 | rclpy.init() 63 | assert rclpy.ok() 64 | rclpy.shutdown() 65 | 66 | 67 | def test_double_init() -> None: 68 | context = rclpy.context.Context() 69 | rclpy.init(context=context) 70 | try: 71 | with pytest.raises(RuntimeError): 72 | rclpy.init(context=context) 73 | finally: 74 | rclpy.shutdown(context=context) 75 | 76 | 77 | def test_double_shutdown() -> None: 78 | context = rclpy.context.Context() 79 | rclpy.init(context=context) 80 | assert context.ok() 81 | rclpy.shutdown(context=context) 82 | with pytest.raises(RuntimeError): 83 | rclpy.shutdown(context=context) 84 | 85 | 86 | def test_create_node_without_init() -> None: 87 | context = rclpy.context.Context() 88 | with pytest.raises(NotInitializedException): 89 | rclpy.create_node('foo', context=context) 90 | 91 | 92 | def test_init_with_domain_id() -> None: 93 | rclpy.init(domain_id=123) 94 | assert rclpy.get_default_context().get_domain_id() == 123 95 | rclpy.shutdown() 96 | context = rclpy.context.Context() 97 | rclpy.init(context=context, domain_id=123) 98 | assert context.get_domain_id() == 123 99 | rclpy.shutdown(context=context) 100 | 101 | 102 | def test_signal_handlers() -> None: 103 | rclpy.init() 104 | assert SignalHandlerOptions.ALL == signals.get_current_signal_handlers_options() 105 | rclpy.shutdown() 106 | assert SignalHandlerOptions.NO == signals.get_current_signal_handlers_options() 107 | 108 | 109 | def test_init_with_invalid_domain_id() -> None: 110 | with pytest.raises(RuntimeError): 111 | rclpy.init(domain_id=-1) 112 | 113 | 114 | def test_managed_init() -> None: 115 | with rclpy.init(domain_id=123) as init: 116 | assert init.context.get_domain_id() == 123 117 | assert init.context.ok() 118 | -------------------------------------------------------------------------------- /rclpy/src/rclpy/serialization.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Open Source Robotics Foundation, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "exceptions.hpp" 26 | #include "serialization.hpp" 27 | #include "utils.hpp" 28 | 29 | namespace rclpy 30 | { 31 | SerializedMessage::SerializedMessage(rcutils_allocator_t allocator) 32 | { 33 | rcl_msg = rmw_get_zero_initialized_serialized_message(); 34 | rcutils_ret_t rcutils_ret = rmw_serialized_message_init(&rcl_msg, 0u, &allocator); 35 | if (RCUTILS_RET_OK != rcutils_ret) { 36 | throw RCUtilsError("failed to initialize serialized message"); 37 | } 38 | } 39 | 40 | SerializedMessage::~SerializedMessage() 41 | { 42 | rcutils_ret_t ret = rmw_serialized_message_fini(&rcl_msg); 43 | if (RCUTILS_RET_OK != ret) { 44 | RCUTILS_SAFE_FWRITE_TO_STDERR( 45 | "[rclpy|" RCUTILS_STRINGIFY(__FILE__) ":" RCUTILS_STRINGIFY(__LINE__) "]: " 46 | "failed to fini rcl_serialized_msg_t in destructor:"); 47 | RCUTILS_SAFE_FWRITE_TO_STDERR(rcutils_get_error_string().str); 48 | RCUTILS_SAFE_FWRITE_TO_STDERR("\n"); 49 | rcutils_reset_error(); 50 | } 51 | } 52 | 53 | py::bytes 54 | serialize(py::object pymsg, py::object pymsg_type) 55 | { 56 | // Get type support 57 | auto ts = static_cast( 58 | common_get_type_support(pymsg_type)); 59 | if (!ts) { 60 | throw py::error_already_set(); 61 | } 62 | 63 | auto ros_msg = convert_from_py(pymsg); 64 | if (!ros_msg) { 65 | throw py::error_already_set(); 66 | } 67 | 68 | // Create a serialized message object 69 | SerializedMessage serialized_msg(rcutils_get_default_allocator()); 70 | 71 | // Serialize 72 | rmw_ret_t rmw_ret = rmw_serialize(ros_msg.get(), ts, &serialized_msg.rcl_msg); 73 | if (RMW_RET_OK != rmw_ret) { 74 | throw RMWError("Failed to serialize ROS message"); 75 | } 76 | 77 | // Bundle serialized message in a bytes object 78 | return py::bytes( 79 | reinterpret_cast(serialized_msg.rcl_msg.buffer), 80 | serialized_msg.rcl_msg.buffer_length); 81 | } 82 | 83 | py::object 84 | deserialize(py::bytes pybuffer, py::object pymsg_type) 85 | { 86 | // Get type support 87 | auto ts = static_cast( 88 | common_get_type_support(pymsg_type)); 89 | if (!ts) { 90 | throw py::error_already_set(); 91 | } 92 | 93 | // Create a serialized message object 94 | rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message(); 95 | // Just copy pointer to avoid extra allocation and copy 96 | char * serialized_buffer; 97 | Py_ssize_t length; 98 | if (PYBIND11_BYTES_AS_STRING_AND_SIZE(pybuffer.ptr(), &serialized_buffer, &length)) { 99 | throw py::error_already_set(); 100 | } 101 | if (length < 0) { 102 | throw py::error_already_set(); 103 | } 104 | serialized_msg.buffer_capacity = length; 105 | serialized_msg.buffer_length = length; 106 | serialized_msg.buffer = reinterpret_cast(serialized_buffer); 107 | 108 | auto deserialized_ros_msg = create_from_py(pymsg_type); 109 | if (!deserialized_ros_msg) { 110 | throw py::error_already_set(); 111 | } 112 | 113 | // Deserialize 114 | rmw_ret_t rmw_ret = rmw_deserialize(&serialized_msg, ts, deserialized_ros_msg.get()); 115 | 116 | if (RMW_RET_OK != rmw_ret) { 117 | throw RMWError("failed to deserialize ROS message"); 118 | } 119 | 120 | return convert_to_py(deserialized_ros_msg.get(), pymsg_type); 121 | } 122 | } // namespace rclpy 123 | --------------------------------------------------------------------------------