From eb33d800ebaecc06940963a41e7c2881d3924e9e Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 06:20:36 +0000 Subject: [PATCH 01/25] Adding devcontainer settings and making some modifications to jazzy --- .devcontainer/postCreateCommand.sh | 3 ++- .devcontainer/serve_sphinx.sh | 4 ++++ docs/source/conf.py | 7 ++++++- docs/source/index.rst | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100755 .devcontainer/serve_sphinx.sh diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index a94bb633..80618592 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -3,4 +3,5 @@ set -e export PIP_BREAK_SYSTEM_PACKAGES=1 pip install -r ../docs/source/requirements.txt -pip install sphinx-autobuild \ No newline at end of file +pip install sphinx-autobuild +chmod +x serve_sphinx.sh \ No newline at end of file diff --git a/.devcontainer/serve_sphinx.sh b/.devcontainer/serve_sphinx.sh new file mode 100755 index 00000000..d79bf0dc --- /dev/null +++ b/.devcontainer/serve_sphinx.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +sphinx-autobuild ../docs/source ../build/html \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index b914e6bc..9b036f81 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,7 +9,7 @@ # -- Project information -project = 'ROS2 Tutorial' +project = "(Murilo's) ROS2 Tutorial" author = 'Murilo M. Marinho' if this_year_str == '2023': @@ -46,6 +46,11 @@ html_theme = 'sphinx_book_theme' # Tried with `furo` on May 23, 2025, but somehow it didn't look right. In particular the download button wasn't as clear. +html_theme_options = { + "announcement": "The documentation is being updated to Jazzy in this branch. " + "See Humble Docs for the stable ones", +} +html_title = project # -- Options for EPUB output epub_show_urls = 'footnote' diff --git a/docs/source/index.rst b/docs/source/index.rst index cff4805b..76ae10db 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,7 +11,7 @@ πŸ“– **About this tutorial** -`ROS2 Humble `_ tutorials by `Murilo M. Marinho `_, focusing on Ubuntu 22.04 x64 LTS and the programming practices of successful state-of-the-art robotics implementations such as the `SmartArmStack `_ and the `AISciencePlatform `_. +`ROS2 Jazzy `_ tutorials by `Murilo M. Marinho `_, focusing on Ubuntu 24.04 x64 LTS and the programming practices of successful state-of-the-art robotics implementations such as the `SmartArmStack `_ also used in the `AISciencePlatform `_. 🀟 **Using this tutorial** From 3bd3ca33c5f8f6b4ffc5a660cbb099ab6e631195 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 06:28:22 +0000 Subject: [PATCH 02/25] [installation.rst] Updating from Humble to Jazzy. --- docs/source/installation.rst | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 45585f66..30866275 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -4,7 +4,7 @@ ROS2 Installation ================= .. note:: - This tutorial is an abridged version of the original `ROS 2 Documentation `_. This tutorial considers a fresh Ubuntu Desktop (not Server) 22.04 LTS x64 (not arm64) installation, that you have super user access and common sense. It might work in other cases, but those have not been tested in this tutorial. + This tutorial is an abridged version of the original `ROS 2 Documentation `_. This tutorial considers a fresh Ubuntu Desktop (not Server) 24.04 LTS installation, that you have super user access and common sense. It might work in other cases, but those have not been tested in this tutorial. .. warning:: All commands must be followed to the letter, in the precise order described herein. Any deviation from what is described might cause unspecified problems and not all of them are easily solvable. @@ -47,8 +47,9 @@ The following commands will do all that magic. .. code-block:: console sudo add-apt-repository universe - sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null + export ROS_APT_SOURCE_VERSION=$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F "tag_name" | awk -F\" '{print $4}') + curl -L -o /tmp/ros2-apt-source.deb "https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo $VERSION_CODENAME)_all.deb" # If using Ubuntu derivates use $UBUNTU_CODENAME + sudo dpkg -i /tmp/ros2-apt-source.deb sudo apt update && sudo apt upgrade -y Install ROS2 packages @@ -58,7 +59,7 @@ There are plenty of ways to install ROS2, the following will suffice for now. .. code-block:: console - sudo apt install -y ros-humble-desktop ros-dev-tools + sudo apt install -y ros-jazzy-desktop ros-dev-tools Set up system environment to find ROS2 ------------------------------------- @@ -71,8 +72,8 @@ The :code:`~/.bashrc` file can be used for that exact purpose as, in Ubuntu, tha .. code-block:: console - echo "# Source ROS2 Humble, as instructed in https://ros2-tutorial.readthedocs.io" >> ~/.bashrc - echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc + echo "# Source ROS2 Jazzy, as instructed in https://ros2-tutorial.readthedocs.io" >> ~/.bashrc + echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc source ~/.bashrc Check if it works @@ -88,18 +89,14 @@ outputs something similar to what is shown below, then it worked! Otherwise, it .. code-block:: console - usage: ros2 [-h] [--use-python-default-buffering] - Call `ros2 -h` for more detailed usage. ... + usage: ros2 [-h] [--use-python-default-buffering] Call `ros2 -h` for more detailed usage. ... ros2 is an extensible command-line tool for ROS 2. options: -h, --help show this help message and exit --use-python-default-buffering - Do not force line buffering in stdout and instead use - the python default buffering, which might be affected - by PYTHONUNBUFFERED/-u and depends on whatever stdout - is interactive or not + Do not force line buffering in stdout and instead use the python default buffering, which might be affected by PYTHONUNBUFFERED/-u and depends on whatever stdout is interactive or not Commands: action Various action related sub-commands @@ -122,8 +119,6 @@ outputs something similar to what is shown below, then it worked! Otherwise, it Call `ros2 -h` for more detailed usage. - - .. _software-properties-common: https://askubuntu.com/questions/1000118/what-is-software-properties-common .. _curl: https://curl.se/ .. _terminator: https://manpages.ubuntu.com/manpages/bionic/man1/terminator.1.html From 31175ffd3c177096814e0dc855e4e689cbbea2df Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 08:18:44 +0000 Subject: [PATCH 03/25] [create_packages.rst] Updated to jazzy --- docs/source/conf.py | 4 +- docs/source/create_packages.rst | 72 +++++++++---------- docs/source/editing_python_source.rst | 3 +- docs/source/preamble.rst | 8 --- .../preamble/python/python_best_practices.rst | 6 +- docs/source/requirements.txt | 1 + docs/source/workspace_setup.rst | 19 +++-- 7 files changed, 53 insertions(+), 60 deletions(-) delete mode 100644 docs/source/preamble.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 9b036f81..73dc38b2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,6 +32,7 @@ 'sphinx_copybutton', # https://sphinx-copybutton.readthedocs.io/en/latest/ 'sphinx_design', # https://sphinx-design.readthedocs.io/en/latest/get_started.html "sphinxext.remoteliteralinclude" # https://github.com/wpilibsuite/sphinxext-remoteliteralinclude + ] intersphinx_mapping = { @@ -48,7 +49,8 @@ # Tried with `furo` on May 23, 2025, but somehow it didn't look right. In particular the download button wasn't as clear. html_theme_options = { "announcement": "The documentation is being updated to Jazzy in this branch. " - "See Humble Docs for the stable ones", + "See Humble Docs for the stable ones. " + "Create an issue for inconsistencies.", } html_title = project diff --git a/docs/source/create_packages.rst b/docs/source/create_packages.rst index c79b231f..422bf00e 100644 --- a/docs/source/create_packages.rst +++ b/docs/source/create_packages.rst @@ -10,43 +10,35 @@ ROS2 has a tool to help create package templates. We can get all available optio which outputs a list of handy options to populate the package template with useful files. Namely, the four emphasized ones. .. code-block:: console - :emphasize-lines: 27, 29, 35, 37 - - usage: ros2 pkg create [-h] [--package-format {2,3}] [--description DESCRIPTION] - [--license LICENSE] - [--destination-directory DESTINATION_DIRECTORY] - [--build-type {cmake,ament_cmake,ament_python}] - [--dependencies DEPENDENCIES [DEPENDENCIES ...]] - [--maintainer-email MAINTAINER_EMAIL] - [--maintainer-name MAINTAINER_NAME] [--node-name NODE_NAME] - [--library-name LIBRARY_NAME] - package_name - - Create a new ROS 2 package - - positional arguments: - package_name The package name - - options: - -h, --help show this help message and exit - --package-format {2,3}, --package_format {2,3} - The package.xml format. - --description DESCRIPTION - The description given in the package.xml - --license LICENSE The license attached to this package; this can be an arbitrary - string, but a LICENSE file will only be generated if it is one - of the supported licenses (pass '?' to get a list) - --destination-directory DESTINATION_DIRECTORY - Directory where to create the package directory - --build-type {cmake,ament_cmake,ament_python} - The build type to process the package with - --dependencies DEPENDENCIES [DEPENDENCIES ...] - list of dependencies - --maintainer-email MAINTAINER_EMAIL - email address of the maintainer of this package - --maintainer-name MAINTAINER_NAME - name of the maintainer of this package - --node-name NODE_NAME - name of the empty executable - --library-name LIBRARY_NAME - name of the empty library + :emphasize-lines: 19,21,27, 29 + + usage: ros2 pkg create [-h] [--package-format {2,3}] [--description DESCRIPTION] [--license LICENSE] [--destination-directory DESTINATION_DIRECTORY] [--build-type {cmake,ament_cmake,ament_python}] + [--dependencies DEPENDENCIES [DEPENDENCIES ...]] [--maintainer-email MAINTAINER_EMAIL] [--maintainer-name MAINTAINER_NAME] [--node-name NODE_NAME] [--library-name LIBRARY_NAME] + package_name + + Create a new ROS 2 package + + positional arguments: + package_name The package name + + options: + -h, --help show this help message and exit + --package-format {2,3}, --package_format {2,3} + The package.xml format. + --description DESCRIPTION + The description given in the package.xml + --license LICENSE The license attached to this package; this can be an arbitrary string, but a LICENSE file will only be generated if it is one of the supported licenses (pass '?' to get a list) + --destination-directory DESTINATION_DIRECTORY + Directory where to create the package directory + --build-type {cmake,ament_cmake,ament_python} + The build type to process the package with + --dependencies DEPENDENCIES [DEPENDENCIES ...] + list of dependencies + --maintainer-email MAINTAINER_EMAIL + email address of the maintainer of this package + --maintainer-name MAINTAINER_NAME + name of the maintainer of this package + --node-name NODE_NAME + name of the empty executable + --library-name LIBRARY_NAME + name of the empty library \ No newline at end of file diff --git a/docs/source/editing_python_source.rst b/docs/source/editing_python_source.rst index 6261265a..688d823e 100644 --- a/docs/source/editing_python_source.rst +++ b/docs/source/editing_python_source.rst @@ -21,9 +21,10 @@ With the project correctly configured, you can 1. move to :menuselection:`src --> python_package_with_a_node --> python_package_with_a_node`. 2. double (left) click :program:`sample_python_node.py` to open the source code, showing the contents of the Node. It is minimal to the point that it doesn't have anything related to :program:`ROS` at all. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_node/sample_python_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_node/python_package_with_a_node/sample_python_node.py :language: python :linenos: + 3. right click :program:`sample_python_node.py` and choose :menuselection:`Debug sample_python_node` It will output in :program:`PyCharm`'s console diff --git a/docs/source/preamble.rst b/docs/source/preamble.rst deleted file mode 100644 index 7f273692..00000000 --- a/docs/source/preamble.rst +++ /dev/null @@ -1,8 +0,0 @@ -Preamble -======== - -.. toctree:: - - preamble/python_basics - preamble/python_best_practices - diff --git a/docs/source/preamble/python/python_best_practices.rst b/docs/source/preamble/python/python_best_practices.rst index 656e3844..aefbef51 100644 --- a/docs/source/preamble/python/python_best_practices.rst +++ b/docs/source/preamble/python/python_best_practices.rst @@ -162,7 +162,7 @@ because our file does not have the permission to run as an executable. To give i and now we can run it properly with -.. code-block:: commandline +.. code-block:: console cd ~/ros2_tutorials_preamble/python/minimalist_package/minimalist_package ./minimalist_script.py @@ -448,14 +448,14 @@ For a quick jolt of instant gratification, let's run the tests before we proceed There are many ways to run tests written with :code:`unittest`. The following will run all tests found in the folder :file:`test` -.. code-block:: commandLine +.. code-block:: console cd ~/ros2_tutorials_preamble/python/minimalist_package python -m unittest discover -v test which will output -.. code-block:: commandLine +.. code-block:: console test_attribute (test_minimalist_class.TestMinimalistClass) ... ok test_get_set_private_attribute (test_minimalist_class.TestMinimalistClass) ... ok diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index afb6a04d..4b74628f 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -4,3 +4,4 @@ sphinx-design sphinx-rtd-theme sphinxext-remoteliteralinclude sphinx-book-theme +sphinxcontrib-globalsubs \ No newline at end of file diff --git a/docs/source/workspace_setup.rst b/docs/source/workspace_setup.rst index 4b30d918..758a2c03 100644 --- a/docs/source/workspace_setup.rst +++ b/docs/source/workspace_setup.rst @@ -36,22 +36,27 @@ for which the output will be something similar to .. code :: console - Summary: 0 packages finished [0.17s] + Summary: 0 packages finished [0.08s] given that we have an empty workspace, no surprise here. The folders :code:`build`, :code:`install`, and :code:`log` have been generated automatically by :program:`colcon`. The project structure becomes as follows. .. code-block:: console - :emphasize-lines: 3,4,5 + :emphasize-lines: 2-4 ros2_tutorial_workspace/ - └── src/ - └── build/ - └── install/ - └── log/ + |-- build + |-- install + |-- log + `-- src -Inside the :code:`install` folder lie all programs etc generated by the project that can be accessed by the users. +Inside the :code:`install` folder lie everything in the project that can be accessed by the users. + +.. note:: + + An easy way to understand if something is not accessible via :program:`ROS2` commands is if they cannot be found + inside your `install` folder. Do the following just once, so that all terminal windows automatically source this new workspace for you. From bf79887f87ef4440aca51acc4ce909c16cc4ccec Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 08:21:44 +0000 Subject: [PATCH 04/25] [create_python_package.rst] Updated to Jazzy. --- docs/source/create_python_package.rst | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/docs/source/create_python_package.rst b/docs/source/create_python_package.rst index 48b5920c..176006f9 100644 --- a/docs/source/create_python_package.rst +++ b/docs/source/create_python_package.rst @@ -29,11 +29,11 @@ which will result in the output below, meaning the package has been generated su going to create a new package package name: the_simplest_python_package - destination directory: /home/murilo/ros2_tutorial_workspace/src + destination directory: /root/ros2_tutorial_workspace/src package format: 3 version: 0.0.0 description: TODO: Package description - maintainer: ['murilo '] + maintainer: ['root '] licenses: ['TODO: License declaration'] build type: ament_python dependencies: [] @@ -52,7 +52,7 @@ which will result in the output below, meaning the package has been generated su creating ./the_simplest_python_package/test/test_pep257.py [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. - It is recommended to use one of the ament license identitifers: + It is recommended to use one of the ament license identifiers: Apache-2.0 BSL-1.0 BSD-2.0 @@ -75,24 +75,8 @@ which will now output .. code :: console Starting >>> the_simplest_python_package - --- stderr: the_simplest_python_package - /usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools. - warnings.warn( - --- - Finished <<< the_simplest_python_package [1.72s] + Finished <<< the_simplest_python_package [0.49s] - Summary: 1 package finished [1.89s] - 1 package had stderr output: the_simplest_python_package + Summary: 1 package finished [0.55s] meaning that :program:`colcon` successfully built the example package. - -.. warning:: - - In this version of ROS2, all :program:`ament_python` packages will output a :code:`SetuptoolsDeprecationWarning`. - This is related to `this issue on Github `_. Until that is fixed, just ignore it. - - - - - - From 43e6ba998c54e10abc271e19ff355b18847a6d9c Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 08:23:18 +0000 Subject: [PATCH 05/25] [create_python_node_with_template.rst] Updated to Jazzy. --- .../create_python_node_with_template.rst | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/source/create_python_node_with_template.rst b/docs/source/create_python_node_with_template.rst index f4360545..f8806fbc 100644 --- a/docs/source/create_python_node_with_template.rst +++ b/docs/source/create_python_node_with_template.rst @@ -26,11 +26,11 @@ Which will output many things in common with the prior example, but with two maj going to create a new package package name: python_package_with_a_node - destination directory: ~/ros2_tutorial_workspace/src + destination directory: /root/ros2_tutorial_workspace/src package format: 3 version: 0.0.0 description: TODO: Package description - maintainer: ['murilo '] + maintainer: ['root '] licenses: ['TODO: License declaration'] build type: ament_python dependencies: [] @@ -51,7 +51,7 @@ Which will output many things in common with the prior example, but with two maj creating ./python_package_with_a_node/python_package_with_a_node/sample_python_node.py [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. - It is recommended to use one of the ament license identitifers: + It is recommended to use one of the ament license identifiers: Apache-2.0 BSL-1.0 BSD-2.0 @@ -73,18 +73,9 @@ which will result in going through the package we created in the prior example a .. code :: console - Starting >>> python_package_with_a_node - Starting >>> the_simplest_python_package - --- stderr: python_package_with_a_node - /usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools. - warnings.warn( - --- - Finished <<< python_package_with_a_node [1.16s] - --- stderr: the_simplest_python_package - /usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools. - warnings.warn( - --- - Finished <<< the_simplest_python_package [1.17s] + Starting >>> python_package_with_a_node + Starting >>> the_simplest_python_package + Finished <<< the_simplest_python_package [0.56s] + Finished <<< python_package_with_a_node [0.56s] - Summary: 2 packages finished [1.36s] - 2 packages had stderr output: python_package_with_a_node the_simplest_python_package + Summary: 2 packages finished [0.62s] From b1a1779d823beb06defbc510dfde60dc3d5c4980 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 08:27:40 +0000 Subject: [PATCH 06/25] [running_node.rst] Updated to Jazzy. --- docs/source/running_node.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/running_node.rst b/docs/source/running_node.rst index fd06a666..c4436bbd 100644 --- a/docs/source/running_node.rst +++ b/docs/source/running_node.rst @@ -27,8 +27,7 @@ which returns the most relevant arguments :code:`package_name` and :code:`execut options: -h, --help show this help message and exit - --prefix PREFIX Prefix command, which should go before the executable. Command must be wrapped - in quotes if it contains spaces (e.g. --prefix 'gdb -ex run --args'). + --prefix PREFIX Prefix command, which should go before the executable. Command must be wrapped in quotes if it contains spaces (e.g. --prefix 'gdb -ex run --args'). Back to our example, with a properly sourced terminal, the example node can be executed with From 4491f5d20fcef5fa484a4240a986af1f0c971225 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sat, 26 Jul 2025 08:31:31 +0000 Subject: [PATCH 07/25] [create_python_node_from_scratch.rst] Updated to Jazzy. --- .../source/create_python_node_from_scratch.rst | 18 ++++++++++-------- .../src/python_package_with_a_node/setup.py | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/source/create_python_node_from_scratch.rst b/docs/source/create_python_node_from_scratch.rst index 85457968..cf45cc17 100644 --- a/docs/source/create_python_node_from_scratch.rst +++ b/docs/source/create_python_node_from_scratch.rst @@ -70,10 +70,11 @@ By now, this should be enough for you to be able to run the node in :program:`Py .. code :: console - [INFO] [1683009340.877110693] [print_forever]: Printed 0 times. - [INFO] [1683009341.336559942] [print_forever]: Printed 1 times. - [INFO] [1683009341.836334639] [print_forever]: Printed 2 times. - [INFO] [1683009342.336555088] [print_forever]: Printed 3 times. + [INFO] [1753518435.025424000] [print_forever]: Printed 0 times. + [INFO] [1753518435.509299083] [print_forever]: Printed 1 times. + [INFO] [1753518436.009566292] [print_forever]: Printed 2 times. + [INFO] [1753518436.509644292] [print_forever]: Printed 3 times. + [INFO] [1753518437.009296251] [print_forever]: Printed 4 times. To finish, press the :guilabel:`Stop` button or press :kbd:`CTRL+F2` on :program:`PyCharm`. The node will exit gracefully with @@ -124,9 +125,10 @@ which will output, as expected .. code :: console - [INFO] [1683010987.130432622] [print_forever]: Printed 0 times. - [INFO] [1683010987.622780292] [print_forever]: Printed 1 times. - [INFO] [1683010988.122731296] [print_forever]: Printed 2 times. - [INFO] [1683010988.622735422] [print_forever]: Printed 3 times. + [INFO] [1753518652.646459087] [print_forever]: Printed 0 times. + [INFO] [1753518653.131078795] [print_forever]: Printed 1 times. + [INFO] [1753518653.632436004] [print_forever]: Printed 2 times. + [INFO] [1753518654.132090212] [print_forever]: Printed 3 times. + [INFO] [1753518654.630924338] [print_forever]: Printed 4 times. To stop, press :kbd:`CTRL+C` on the terminal and the Node will return gracefully. diff --git a/ros2_tutorial_workspace/src/python_package_with_a_node/setup.py b/ros2_tutorial_workspace/src/python_package_with_a_node/setup.py index 6eb24b24..490f1e83 100644 --- a/ros2_tutorial_workspace/src/python_package_with_a_node/setup.py +++ b/ros2_tutorial_workspace/src/python_package_with_a_node/setup.py @@ -1,11 +1,11 @@ -from setuptools import setup +from setuptools import find_packages, setup package_name = 'python_package_with_a_node' setup( name=package_name, version='0.0.0', - packages=[package_name], + packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), From 74478bd8e5dc50c39abf90d65f913a1bcc210033 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sun, 27 Jul 2025 02:58:39 +0000 Subject: [PATCH 08/25] Fixing warnings in multiple files --- docs/source/create_packages.rst | 2 +- .../create_python_node_with_template.rst | 6 +----- docs/source/python_node_explained.rst | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/source/create_packages.rst b/docs/source/create_packages.rst index 422bf00e..cff72696 100644 --- a/docs/source/create_packages.rst +++ b/docs/source/create_packages.rst @@ -1,5 +1,5 @@ Create packages (:program:`ros2 pkg create`) ---------------- +-------------------------------------------- ROS2 has a tool to help create package templates. We can get all available options by running diff --git a/docs/source/create_python_node_with_template.rst b/docs/source/create_python_node_with_template.rst index f8806fbc..ac927920 100644 --- a/docs/source/create_python_node_with_template.rst +++ b/docs/source/create_python_node_with_template.rst @@ -14,11 +14,7 @@ Let us use the template for creating a package with a Node, as follows. Which will output many things in common with the prior example, but with two major differences. -#. It generates a template Node - - .. code-block:: console - creating ./python_package_with_a_node/python_package_with_a_node/sample_python_node.py - +#. It generates a template Node #. The :file:`setup.py` has information about the Node. .. code-block:: bash diff --git a/docs/source/python_node_explained.rst b/docs/source/python_node_explained.rst index 6ee6ef24..0155d628 100644 --- a/docs/source/python_node_explained.rst +++ b/docs/source/python_node_explained.rst @@ -76,7 +76,9 @@ Have a :code:`try-catch` block for :code:`KeyboardInterrupt` You can see more about this topic at :ref:`Python try catch`, in the preamble. -In the current version of the `official ROS2 examples `_, for reasons beyond my comprehension, this step is not followed. +.. important:: + + In the current version of the `official ROS2 examples `_, for reasons beyond my comprehension, this step is not followed. However, when running Nodes either in the terminal or in :program:`PyCharm`, catching a :code:`KeyboardInterrupt` is the only reliable way to finish the Nodes cleanly. A :code:`KeyboardInterrupt` is emitted at a terminal by pressing :kbd:`CTRL+C`, whereas it is emitted by :program:`PyCharm` when pressing :guilabel:`Stop`. @@ -87,6 +89,20 @@ That is particularly important when real robots need to be gracefully shut down :lines: 43-58 :emphasize-lines: 7,13 +.. important:: + + Despite what is shown in the official ROS2 examples, do not add these to your code. This will cause the node to crash when + shutting down and not return 0 as expected by anything automatic running your nodes. This can cause many confusing behaviours. + + .. code-block:: python + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + minimal_publisher.destroy_node() + rclpy.shutdown() + + Document your code with Docstrings ---------------------------------- From 0be2e1cd465bb8dba80f43aaee0ff934aded194c Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sun, 27 Jul 2025 03:09:09 +0000 Subject: [PATCH 09/25] [create_python_library.rst] Adjusted for Jazzy. --- docs/source/create_python_library.rst | 90 +++++++++++---------------- 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/docs/source/create_python_library.rst b/docs/source/create_python_library.rst index 342818c4..0c92133e 100644 --- a/docs/source/create_python_library.rst +++ b/docs/source/create_python_library.rst @@ -17,11 +17,11 @@ which outputs the forever beautiful wall of text we're now used to, with a minor going to create a new package package name: python_package_with_a_library - destination directory: /home/murilo/git/ROS2_Tutorial/ros2_tutorial_workspace/src + destination directory: /root/ros2_tutorial_workspace/src package format: 3 version: 0.0.0 description: TODO: Package description - maintainer: ['murilo '] + maintainer: ['root '] licenses: ['TODO: License declaration'] build type: ament_python dependencies: [] @@ -43,7 +43,7 @@ which outputs the forever beautiful wall of text we're now used to, with a minor creating ./python_package_with_a_library/python_package_with_a_library/sample_python_library/__init__.py [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. - It is recommended to use one of the ament license identitifers: + It is recommended to use one of the ament license identifiers: Apache-2.0 BSL-1.0 BSD-2.0 @@ -58,21 +58,25 @@ The folders/files, Mason, what do they mean? -------------------------------------------- The ROS2 package created from the template has a structure like so. In particular, we can see that :file:`python_package_with_a_library` is repeated twice in a row. This is a common source of error, so don't forget! +The first is the name of the :program:`ROS2` package, and the second is the name of Python package that will be installed by :program:`ROS2`. .. code-block:: console - :emphasize-lines: 1,2 + :emphasize-lines: 1,3 - python_package_with_a_library - └── python_package_with_a_library - └── sample_python_library - __init__.py - __init__.py - └── resource - python_package_with_a_library - └── test - package.xml - setup.cfg - setup.py + python_package_with_a_library/ + |-- package.xml + |-- python_package_with_a_library + | |-- __init__.py + | `-- sample_python_library + | `-- __init__.py + |-- resource + | `-- python_package_with_a_library + |-- setup.cfg + |-- setup.py + `-- test + |-- test_copyright.py + |-- test_flake8.py + `-- test_pep257.py We learned the meaning of most of those in the preamble, namely :ref:`Python Best Practices`. To quickly clarify a few things, see the table below. @@ -105,17 +109,24 @@ Overview of the library For the sake of the example, let us create a library with a Python :code:`function` and another one with a :code:`class`. To guide our next steps, we first draw a quick overview of what our :code:`python_package_with_a_library` will look like. .. code-block:: console - :emphasize-lines: 4,5,6 + :emphasize-lines: 6-8 - python_package_with_a_library - └── python_package_with_a_library - └── sample_python_library - __init__.py - _sample_class.py - _sample_function.py - __init__.py - └── resource - └── test + python_package_with_a_library/ + |-- package.xml + |-- python_package_with_a_library + | |-- __init__.py + | `-- sample_python_library + | |-- __init__.py + | |-- _sample_class.py + | `-- _sample_function.py + |-- resource + | `-- python_package_with_a_library + |-- setup.cfg + |-- setup.py + `-- test + |-- test_copyright.py + |-- test_flake8.py + `-- test_pep257.py With respect to the highlighted files, we will @@ -152,35 +163,6 @@ Create a new file with the following contents and name. :lines: 26- The class is quite simple with a `private data member `_ and a method to retrieve it. - -Modify the :code:`__init__.py` to export the symbols ----------------------------------------------------- - -With the necessary files created and properly organized, the last step is to :code:`import` the function and the class. We modify proper :file:`__init__.py` file with the following contents. - -:download:`~/ros2_tutorial_workspace/src/python_package_with_a_library/python_package_with_a_library/sample_python_library/__init__.py <../../ros2_tutorial_workspace/src/python_package_with_a_library/python_package_with_a_library/sample_python_library/__init__.py>` - -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_library/python_package_with_a_library/sample_python_library/__init__.py - :language: python - :linenos: - :lines: 24- - -Modify the :code:`setup.py` to export the packages --------------------------------------------------- - -.. warning:: - This step might be unnecessary after `this fix `_. - -.. note:: - - This is a *one-size-fits-most* solution, which might not work for certain Python package structures. As a generic solution, we will export all Python packages in the ROS2 package excluding the `test` directory. For more information on :program:`setuptools`, see the `official Python packaging docs `_. - -:download:`~/ros2_tutorial_workspace/src/python_package_with_a_library/setup.py <../../ros2_tutorial_workspace/src/python_package_with_a_library/setup.py>` - -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_library/setup.py - :language: python - :linenos: - :emphasize-lines: 1,8 Build and source ---------------- From 643c80312b4ab8dd81e434dab2fbb78ce9e42291 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sun, 27 Jul 2025 03:11:30 +0000 Subject: [PATCH 10/25] [using_python_library.rst] Updating to Jazzy. --- docs/source/using_python_library.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/source/using_python_library.rst b/docs/source/using_python_library.rst index e3e09191..3ecfdaa4 100644 --- a/docs/source/using_python_library.rst +++ b/docs/source/using_python_library.rst @@ -22,11 +22,11 @@ resulting in yet another version of our favorite wall of text going to create a new package package name: python_package_that_uses_the_library - destination directory: /home/murilo/ros2_tutorial_workspace/src + destination directory: /root/ros2_tutorial_workspace/src package format: 3 version: 0.0.0 description: TODO: Package description - maintainer: ['murilo '] + maintainer: ['root '] licenses: ['TODO: License declaration'] build type: ament_python dependencies: ['rclpy', 'python_package_with_a_library'] @@ -47,7 +47,7 @@ resulting in yet another version of our favorite wall of text creating ./python_package_that_uses_the_library/python_package_that_uses_the_library/node_that_uses_the_library.py [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. - It is recommended to use one of the ament license identitifers: + It is recommended to use one of the ament license identifiers: Apache-2.0 BSL-1.0 BSD-2.0 @@ -105,10 +105,12 @@ Which outputs something similar to the shown below, but with different numbers a .. code :: console - [INFO] [1683598288.149167944] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.19395834493833486,1.3891603395040568) returned 2.506264769030609. - [INFO] [1683598288.149643378] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned qyOXLBEtzZ. - [INFO] [1683598288.616095880] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.7387236329957096,1.7650481260672202) returned 6.2688730214810775. - [INFO] [1683598288.616604769] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned LCFNFyzwhk. - [INFO] [1683598289.116050219] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.003813494022560704,1.7056916575839387) returned 2.9224078633691604. - [INFO] [1683598289.116553899] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned wrtTlOdanZ. + [INFO] [1753585839.509922172] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.9787232004970391,1.7320908702316369) returned 7.348512926060575. + [INFO] [1753585839.510400755] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned GQkUZgSkje. + [INFO] [1753585839.993999505] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.7637556876478347,1.0377535114838756) returned 3.2454353945561767. + [INFO] [1753585839.994351922] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned EztAWYuFMB. + [INFO] [1753585840.494895006] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.26638758438777976,1.1809445792770386) returned 2.0947703919786846. + [INFO] [1753585840.495950422] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned ITLIHPOMgv. + [INFO] [1753585840.994468589] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.5244531764161572,1.7524376840394509) returned 5.184231990426279. + [INFO] [1753585840.994695797] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned LGtybBngKv. From 2dc4fbc73bcf4063095b262113a0c5c444eaca6f Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sun, 27 Jul 2025 03:15:53 +0000 Subject: [PATCH 11/25] messages.rst Updated to Jazzy. --- docs/source/messages.rst | 58 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/source/messages.rst b/docs/source/messages.rst index 81605260..f789cbfb 100644 --- a/docs/source/messages.rst +++ b/docs/source/messages.rst @@ -1,16 +1,16 @@ Messages and Services (:program:`ros2 interface`) ================================================= -If by now you haven't particularly fallen in love with ROS2, fear not. Indeed, we haven't done much so far that couldn't be achieved more easily by other means. +If by now you haven't particularly fallen in love with :program:`ROS2`, fear not. Indeed, we haven't done much so far that couldn't be achieved more easily by other means. -ROS2 begins to shine most in its interprocess communication, through what are called `ROS2 interfaces `_. In particular, the fact that we can easily interface Nodes written in Python and C++ is a strong selling point. +:program:`ROS2` begins to shine most in its interprocess communication, through what are called `ROS2 interfaces `_. In particular, the fact that we can easily interface Nodes written in Python and C++ is a strong selling point. :code:`Messages` are one of the three types of ROS2 interfaces. This will most likely be the standard of communication between Nodes in your packages. We will also see the bidirectional :code:`Services` now. The last type of interface, :code:`Actions`, is left for another section. Description ----------- -In ROS2, interfaces are files written in the ROS2 :abbr:`IDL (Interface Description Language)`. Each type of interface is described in a :file:`.msg` file (or :file:`.srv` file), which is then built by :program:`colcon` into libraries that can be imported into your Python programs. +In :program:`ROS2`, interfaces are files written in the ROS2 :abbr:`IDL (Interface Description Language)`. Each type of interface is described in a :file:`.msg` file (or :file:`.srv` file), which is then built by :program:`colcon` into libraries that can be imported into your Python programs. When dealing with common robotics concepts such as geometric and sensor messages, it is good practice to use interfaces that already exist in ROS2, instead of creating new ones that serve the exact same purpose. In addition, for complicated interfaces, we can combine existing ones for simplicity. @@ -52,7 +52,7 @@ This shows that with :program:`ros2 interface list` we can get a list of all int to get the list of packages with interfaces available, which returns something similar to .. code-block:: console - :emphasize-lines: 8, 19 + :emphasize-lines: 8, 21 action_msgs action_tutorials_interfaces @@ -66,13 +66,16 @@ to get the list of packages with interfaces available, which returns something s logging_demo map_msgs nav_msgs + package_with_interfaces pcl_msgs pendulum_msgs rcl_interfaces rmw_dds_common rosbag2_interfaces rosgraph_msgs + sas_msgs sensor_msgs + service_msgs shape_msgs statistics_msgs std_msgs @@ -81,6 +84,7 @@ to get the list of packages with interfaces available, which returns something s tf2_msgs trajectory_msgs turtlesim + type_description_interfaces unique_identifier_msgs visualization_msgs @@ -101,39 +105,39 @@ which returns .. code:: console + example_interfaces/msg/UInt16 + example_interfaces/msg/Empty + example_interfaces/action/Fibonacci example_interfaces/msg/String - example_interfaces/srv/AddTwoInts - example_interfaces/srv/SetBool - example_interfaces/msg/UInt8 - example_interfaces/msg/Int64MultiArray - example_interfaces/msg/Byte - example_interfaces/msg/Float32 - example_interfaces/msg/Int64 + example_interfaces/msg/Int32 example_interfaces/msg/UInt32MultiArray - example_interfaces/msg/Int32MultiArray - example_interfaces/msg/Empty + example_interfaces/msg/Float64MultiArray example_interfaces/msg/Float32MultiArray + example_interfaces/srv/AddTwoInts + example_interfaces/msg/UInt8MultiArray + example_interfaces/msg/Int8 example_interfaces/msg/Int16MultiArray - example_interfaces/action/Fibonacci - example_interfaces/msg/UInt16MultiArray + example_interfaces/msg/UInt32 + example_interfaces/srv/SetBool + example_interfaces/msg/Int64 + example_interfaces/msg/MultiArrayDimension example_interfaces/msg/Int8MultiArray - example_interfaces/msg/Bool example_interfaces/msg/ByteMultiArray - example_interfaces/msg/MultiArrayLayout - example_interfaces/msg/UInt8MultiArray - example_interfaces/msg/UInt16 + example_interfaces/msg/Int32MultiArray + example_interfaces/srv/Trigger + example_interfaces/msg/Int64MultiArray + example_interfaces/msg/Float64 + example_interfaces/msg/Byte example_interfaces/msg/Int16 - example_interfaces/msg/Int8 - example_interfaces/msg/MultiArrayDimension + example_interfaces/msg/UInt16MultiArray + example_interfaces/msg/UInt64MultiArray example_interfaces/msg/Char - example_interfaces/msg/Float64 - example_interfaces/srv/Trigger + example_interfaces/msg/UInt8 + example_interfaces/msg/Bool example_interfaces/msg/UInt64 example_interfaces/msg/WString - example_interfaces/msg/Int32 - example_interfaces/msg/Float64MultiArray - example_interfaces/msg/UInt64MultiArray - example_interfaces/msg/UInt32 + example_interfaces/msg/MultiArrayLayout + example_interfaces/msg/Float32 Messages -------- From b73b51a94c57d01bdfba89e04d55b2d027605491 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Sun, 27 Jul 2025 03:20:09 +0000 Subject: [PATCH 12/25] [create_interface_package.rst] Updated to Jazzy --- docs/source/create_interface_package.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/create_interface_package.rst b/docs/source/create_interface_package.rst index 976433c3..72b2bb74 100644 --- a/docs/source/create_interface_package.rst +++ b/docs/source/create_interface_package.rst @@ -28,14 +28,14 @@ which again shows our beloved wall of text, with a few highlighted differences b going to create a new package package name: package_with_interfaces - destination directory: /home/murilo/git/ROS2_Tutorial/ros2_tutorial_workspace/src + destination directory: /root/ros2_tutorial_workspace/src package format: 3 version: 0.0.0 description: TODO: Package description - maintainer: ['murilo '] + maintainer: ['root '] licenses: ['TODO: License declaration'] build type: ament_cmake - dependencies: [geometry_msgs] + dependencies: ['geometry_msgs'] creating folder ./package_with_interfaces creating ./package_with_interfaces/package.xml creating source and include folder @@ -44,7 +44,7 @@ which again shows our beloved wall of text, with a few highlighted differences b creating ./package_with_interfaces/CMakeLists.txt [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. - It is recommended to use one of the ament license identitifers: + It is recommended to use one of the ament license identifiers: Apache-2.0 BSL-1.0 BSD-2.0 @@ -63,7 +63,7 @@ The :file:`package.xml` dependencies Whenever the package has any type of interface, the :file:`package.xml` **must** include three specific dependencies. Namely, the ones highlighted below. Edit the :file:`package_with_interfaces/package.xml` like so -:download:`package.xml <../../ros2_tutorial_workspace/src/package_with_interfaces/package.xml>` +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/package.xml <../../ros2_tutorial_workspace/src/package_with_interfaces/package.xml>` .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/package.xml :language: xml @@ -98,7 +98,7 @@ Let us create a message file to transfer inspirational quotes between Nodes. For There are many ways to represent this, but for the sake of the example let us give each message an :code:`id` and two rather obvious fields. Create a file called :file:`AmazingQuote.msg` in the folder :file:`msg` that we just created with the following contents. -:download:`AmazingQuote.msg <../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg>` +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg <../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg>` .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg :language: yaml @@ -123,7 +123,7 @@ With the :file:`AmazingQuote.msg`, we have seen how to use built-in types. Let's Add the file :file:`WhatIsThePoint.srv` in the :file:`srv` folder with the following contents -:download:`WhatIsThePoint.srv <../../ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv>` +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv <../../ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv>` .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv :language: yaml @@ -141,7 +141,7 @@ The :file:`CMakeLists.txt` directives If a package is dedicated to interfaces, there is no need to worry too much about the :program:`CMake` details. We can follow the boilerplate as shown below. Edit the :file:`package_with_interfaces/CMakeLists.txt` like so -:download:`CMakeLists.txt <../../ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt>` +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt <../../ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt>` .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt :language: cmake From eecc0317706e2bb8ade2b3d1f39aee6412a32c0e Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 00:48:18 +0000 Subject: [PATCH 13/25] Adding mermaid diagram with dark mode. --- .readthedocs.yaml | 5 ++++- docs/source/conf.py | 7 +++++-- docs/source/peppeteer-config.json | 3 +++ docs/source/publishers_and_subscribers.rst | 23 +++++++++++++++++----- docs/source/requirements.txt | 2 +- 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 docs/source/peppeteer-config.json diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b0d7e88e..d19fd641 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,9 +11,12 @@ build: tools: python: "3.11" # You can also specify other tool versions: - # nodejs: "19" + nodejs: "19" # rust: "1.64" # golang: "1.19" + jobs: + post_install: + - npm install -g @mermaid-js/mermaid-cli # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/docs/source/conf.py b/docs/source/conf.py index 73dc38b2..c8d12b61 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,8 +31,8 @@ 'hoverxref.extension', # https://sphinx-hoverxref.readthedocs.io/en/latest/index.html 'sphinx_copybutton', # https://sphinx-copybutton.readthedocs.io/en/latest/ 'sphinx_design', # https://sphinx-design.readthedocs.io/en/latest/get_started.html - "sphinxext.remoteliteralinclude" # https://github.com/wpilibsuite/sphinxext-remoteliteralinclude - + 'sphinxext.remoteliteralinclude', # https://github.com/wpilibsuite/sphinxext-remoteliteralinclude + 'sphinxcontrib.mermaid' #https://sphinxcontrib-mermaid-demo.readthedocs.io/en/latest/ ] intersphinx_mapping = { @@ -60,6 +60,9 @@ # -- Options for hoverxref.extension https://sphinx-hoverxref.readthedocs.io/en/latest/configuration.html hoverxref_auto_ref = True +# https://sphinxcontrib-mermaid-demo.readthedocs.io/en/latest/ +mermaid_params = ['-p' 'puppeteer-config.json'] + # -- Options for latex https://docs.readthedocs.io/en/stable/guides/pdf-non-ascii-languages.html # latex_engine = "xelatex" # By using this, the UTF emojis became question marks in the PDF. diff --git a/docs/source/peppeteer-config.json b/docs/source/peppeteer-config.json new file mode 100644 index 00000000..dce67b8e --- /dev/null +++ b/docs/source/peppeteer-config.json @@ -0,0 +1,3 @@ +{ + "args": ["--no-sandbox"] +} \ No newline at end of file diff --git a/docs/source/publishers_and_subscribers.rst b/docs/source/publishers_and_subscribers.rst index b84e50e2..60c6c6f6 100644 --- a/docs/source/publishers_and_subscribers.rst +++ b/docs/source/publishers_and_subscribers.rst @@ -5,17 +5,30 @@ Publishers and Subscribers: using messages Except for the particulars of the :file:`setup.py` file, the way that publishers and subscribers in ROS2 work in Python, i.e. the explanation in this section, does not depend on :program:`ament_python` or :program:`ament_cmake`. -Finally, we reached the point where ROS2 becomes appealing. As you saw in the last section, we can easily create complex interface types using an easy and generic description. +Finally, we reached the point where :program:`ROS2` becomes appealing. As you saw in the last section, we can easily create complex interface types using an easy and generic description. We can use those to provide `interprocess communication `_, i.e. two different programs talking to each other, which otherwise can be error-prone and very difficult to implement. -ROS2 works on a model in which any number of processes can communicate over a :code:`Topic` that only accepts one message type. Each topic is uniquely identified by a string. +:program:`ROS2` messages work on a model in which any number of processes can communicate over a :code:`Topic` that only accepts one message type. Each topic is uniquely identified by a string. Then -- A program that sends (publishes) information to the topic has a :code:`Publisher`. -- A program that reads (subscribes) information from a topic has a :code:`Subscriber`. +- A program that sends (publishes) information to the topic has one or more :code:`Publisher` \(s). +- A program that reads (subscribes) information from a topic has one or more :code:`Subscriber` \(s). -Each Node can have any number of :code:`Publishers` and :code:`Subscribers` and a combination thereof, connecting to an arbitrary number of Nodes. This forms part of the connections in the so-called `ROS graph `_. +Each Node can have any number of :code:`Publishers` and :code:`Subscribers` and a combination thereof, connecting to an arbitrary number of Nodes. This forms part of the connections in the so-called `ROS graph `_. An example is shown below. + +.. mermaid:: + + %%{init: { "theme" : "dark" }}%% + graph LR; + A[Publisher #1] --> B[Topic] + C[Publisher #2] --> B + B --> D[Subscriber #1] + +.. note:: + + This is an abstraction. As long as the information flows in this manner, it does not mean that an entity called ``topic`` must exist. + In :program:`ROS2`, this type of communication happens, in fact, peer-to-peer. Create the package ------------------ diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index 4b74628f..fe98e99e 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -4,4 +4,4 @@ sphinx-design sphinx-rtd-theme sphinxext-remoteliteralinclude sphinx-book-theme -sphinxcontrib-globalsubs \ No newline at end of file +sphinxcontrib-mermaid \ No newline at end of file From 6fcd892298aed65cfdb4012759b4e4f1f3687355 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 00:55:09 +0000 Subject: [PATCH 14/25] [publishers_and_subscribers.rst] Updated to Jazzy. --- docs/source/publishers_and_subscribers.rst | 83 +++++++++++++------ .../setup.py | 8 +- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/docs/source/publishers_and_subscribers.rst b/docs/source/publishers_and_subscribers.rst index 60c6c6f6..a2edc87a 100644 --- a/docs/source/publishers_and_subscribers.rst +++ b/docs/source/publishers_and_subscribers.rst @@ -42,6 +42,46 @@ First, let us create an :program:`ament_python` package that depends on our newl --build-type ament_python \ --dependencies rclpy package_with_interfaces +.. dropdown:: ros2 pkg create output + + .. code :: console + + going to create a new package + package name: python_package_that_uses_the_messages + destination directory: /root/ros2_tutorial_workspace/src + package format: 3 + version: 0.0.0 + description: TODO: Package description + maintainer: ['root '] + licenses: ['TODO: License declaration'] + build type: ament_python + dependencies: ['rclpy', 'package_with_interfaces'] + creating folder ./python_package_that_uses_the_messages + creating ./python_package_that_uses_the_messages/package.xml + creating source folder + creating folder ./python_package_that_uses_the_messages/python_package_that_uses_the_messages + creating ./python_package_that_uses_the_messages/setup.py + creating ./python_package_that_uses_the_messages/setup.cfg + creating folder ./python_package_that_uses_the_messages/resource + creating ./python_package_that_uses_the_messages/resource/python_package_that_uses_the_messages + creating ./python_package_that_uses_the_messages/python_package_that_uses_the_messages/__init__.py + creating folder ./python_package_that_uses_the_messages/test + creating ./python_package_that_uses_the_messages/test/test_copyright.py + creating ./python_package_that_uses_the_messages/test/test_flake8.py + creating ./python_package_that_uses_the_messages/test/test_pep257.py + + [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. + It is recommended to use one of the ament license identifiers: + Apache-2.0 + BSL-1.0 + BSD-2.0 + BSD-2-Clause + BSD-3-Clause + GPL-3.0-only + LGPL-3.0-only + MIT + MIT-0 + Overview -------- @@ -147,7 +187,7 @@ For the subscriber Node, create a file in :file:`python_package_that_uses_the_me :language: python :linenos: :lines: 24- - :emphasize-lines: 3, 11-15, 17-30 + :emphasize-lines: 3, 11-15, 17-31 Similarly to the publisher, in the subscriber, we start by importing the message in question @@ -168,7 +208,7 @@ where the only difference with respect to the publisher is the third argument, n .. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_messages/python_package_that_uses_the_messages/amazing_quote_subscriber_node.py :language: python :lines: 40-53 - :emphasize-lines: 7,11,13 + :emphasize-lines: 8,12,14 That callback method will be automatically called by ROS2, as one of the tasks performed by :code:`rclpy.spin(Node)`. Depending on the :code:`qos_profile`, it will not necessarily be the latest message. @@ -220,41 +260,30 @@ which outputs .. code:: console - [INFO] [1684215672.344532584] [amazing_quote_subscriber_node]: - I have received the most amazing of quotes. - It says - - 'Use the force, Pikachu!' - - And was thought by the following genius - - -- Uncle Ben - - This latest quote had the id=3. + [INFO] [1753664072.638312553] [amazing_quote_subscriber_node]: + I have received the most amazing of quotes. + It says - [INFO] [1684215672.844618237] [amazing_quote_subscriber_node]: - I have received the most amazing of quotes. - It says + 'Use the force, Pikachu!' - 'Use the force, Pikachu!' + And was thought by the following genius - And was thought by the following genius + -- Uncle Ben - -- Uncle Ben + This latest quote had the id=37. - This latest quote had the id=4. + [INFO] [1753664073.121886428] [amazing_quote_subscriber_node]: + I have received the most amazing of quotes. + It says - [INFO] [1684215673.344514856] [amazing_quote_subscriber_node]: - I have received the most amazing of quotes. - It says + 'Use the force, Pikachu!' - 'Use the force, Pikachu!' + And was thought by the following genius - And was thought by the following genius + -- Uncle Ben - -- Uncle Ben + This latest quote had the id=38. - This latest quote had the id=5. .. note:: diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_messages/setup.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_messages/setup.py index 3f8c58fb..fe716f73 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_messages/setup.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_messages/setup.py @@ -1,11 +1,11 @@ -from setuptools import setup +from setuptools import find_packages, setup package_name = 'python_package_that_uses_the_messages' setup( name=package_name, version='0.0.0', - packages=[package_name], + packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), @@ -13,8 +13,8 @@ ], install_requires=['setuptools'], zip_safe=True, - maintainer='murilo', - maintainer_email='murilomarinho@ieee.org', + maintainer='root', + maintainer_email='murilo.marinho@manchester.ac.uk', description='TODO: Package description', license='TODO: License declaration', tests_require=['pytest'], From 45cd34d4f446ab52affdac65027fed3ffa7e62ff Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 01:29:18 +0000 Subject: [PATCH 15/25] Removing WhatIsThePoint, adding new message to keep teachable points, adding AddPoints to use as service for the tutorial. --- docs/source/create_interface_package.rst | 73 +++++++++++++------ docs/source/service_servers_and_clients.rst | 44 +++++++++++ .../package_with_interfaces/CMakeLists.txt | 3 +- .../msg/AmazingQuoteStamped.msg | 4 + .../package_with_interfaces/srv/AddPoints.srv | 6 ++ .../srv/WhatIsThePoint.srv | 5 -- 6 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg create mode 100644 ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv delete mode 100644 ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv diff --git a/docs/source/create_interface_package.rst b/docs/source/create_interface_package.rst index 72b2bb74..587995f9 100644 --- a/docs/source/create_interface_package.rst +++ b/docs/source/create_interface_package.rst @@ -5,6 +5,10 @@ Creating a dedicated package for custom interfaces Despite this push in ROS2 towards having the users define even the simplest of message types, to define new interfaces in ROS2 we must use an :program:`ament_cmake` package. It **cannot** be done with an :program:`ament_python` package. +.. seealso:: + + The contents of this session were simplified in this version. A more complex example is shown in https://ros2-tutorial.readthedocs.io/en/humble/service_servers_and_clients.html. + All interfaces in ROS2 must be made in an :program:`ament_cmake` package. We have so far not needed it, but for this scenario we cannot escape. Thankfully, for this we don't need to dig too deep into :program:`CMake`, so fear not. Creating the package @@ -104,6 +108,22 @@ Create a file called :file:`AmazingQuote.msg` in the folder :file:`msg` that we :language: yaml :linenos: +Re-using a message from the same package +++++++++++++++++++++++++++++++++++++++++ + +With the :file:`AmazingQuote.msg`, we have seen how to use built-in types. Let's use another message, :file:`AmazingQuoteStamped.msg`, to learn two more possibilities, namely using messages from the same package and messages defined elsewhere. + +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg <../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg>` + +.. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg + :language: yaml + :linenos: + +Note that if the message is defined in the same package, the package name does not appear in the message (or service) definition. If the message is defined elsewhere, we have to fully specify the package. + +In many :program:`ROS2` packages, messages with the suffix ``Stamped`` exist. As a rule, those are the same messages but with a additional +``std_msgs/Header`` so that they can be timestamped. + The service folder ------------------ @@ -119,17 +139,18 @@ The convention is to add all services to a folder called :file:`srv`. Let's foll The service file ---------------- -With the :file:`AmazingQuote.msg`, we have seen how to use built-in types. Let's use the service to learn two more possibilities. Let us use a message from the same package and a message from another package. Services cannot be used to define other services. +.. note:: + + Services cannot be used to define other services. -Add the file :file:`WhatIsThePoint.srv` in the :file:`srv` folder with the following contents +Add the file :file:`AddPoints.srv` in the :file:`srv` folder with the following contents -:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv <../../ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv>` +:download:`~/ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv <../../ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv>` -.. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv +.. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv :language: yaml :linenos: -Note that if the message is defined in the same package, the package name does not appear in the service or message definition. If the message is defined elsewhere, we have to specify it. The :file:`CMakeLists.txt` directives ------------------------------------- @@ -146,7 +167,7 @@ Edit the :file:`package_with_interfaces/CMakeLists.txt` like so .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt :language: cmake :linenos: - :emphasize-lines: 14-36 + :emphasize-lines: 14-37 What to do when adding new interfaces? -------------------------------------- @@ -167,8 +188,8 @@ If additional interfaces are required .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt :language: cmake - :lines: 17-24 - :emphasize-lines: 4, 7 + :lines: 17-25 + :emphasize-lines: 5, 8 .. note:: @@ -196,8 +217,9 @@ returns .. code:: console - package_with_interfaces/srv/WhatIsThePoint - package_with_interfaces/msg/AmazingQuote + package_with_interfaces/msg/AmazingQuote + package_with_interfaces/msg/AmazingQuoteStamped + package_with_interfaces/srv/AddPoints and we can further get more specific info on :file:`AmazingQuote.msg` @@ -210,26 +232,29 @@ which returns .. literalinclude:: ../../ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuote.msg :language: yaml -alternatively, we can do the same for :file:`WhatIsThePoint.srv` +alternatively, we can do the same for :file:`AddPoints.srv` .. code:: console - ros2 interface show package_with_interfaces/srv/WhatIsThePoint + ros2 interface show package_with_interfaces/srv/AddPoints which returns expanded information on each field of the service - .. code:: yaml - # WhatIsThePoint.srv from https://ros2-tutorial.readthedocs.io - # Receives an AmazingQuote and returns what is the point - AmazingQuote quote - int32 id - string quote - string philosopher_name - --- - geometry_msgs/Point point - float64 x - float64 y - float64 z + # AddPoints.srv from https://ros2-tutorial.readthedocs.io + # Adds the values of points `a` and `b` to give the output `result` + geometry_msgs/Point a + float64 x + float64 y + float64 z + geometry_msgs/Point b + float64 x + float64 y + float64 z + --- + geometry_msgs/Point result + float64 x + float64 y + float64 z diff --git a/docs/source/service_servers_and_clients.rst b/docs/source/service_servers_and_clients.rst index b7da5d54..6c89c592 100644 --- a/docs/source/service_servers_and_clients.rst +++ b/docs/source/service_servers_and_clients.rst @@ -6,6 +6,10 @@ At your Service: Servers and Clients Except for the particulars of the :file:`setup.py` file, the way that services in ROS2 work in Python, i.e. the explanation in this section, does not depend on :program:`ament_python` or :program:`ament_cmake`. +.. seealso:: + + The contents of this session were simplified in this version. A more complex example is shown in https://ros2-tutorial.readthedocs.io/en/humble/service_servers_and_clients.html. + In some cases, we need means of communication in which each command has an associated response. That is where :code:`Services` come into play. Create the package @@ -20,6 +24,46 @@ We start by creating a package to use the :code:`Service` we first created in :r --build-type ament_python \ --dependencies rclpy package_with_interfaces +.. dropdown:: ros2 pkg create output + + .. code :: console + + going to create a new package + package name: python_package_that_uses_the_services + destination directory: /root/ros2_tutorial_workspace/src + package format: 3 + version: 0.0.0 + description: TODO: Package description + maintainer: ['root '] + licenses: ['TODO: License declaration'] + build type: ament_python + dependencies: ['rclpy', 'package_with_interfaces'] + creating folder ./python_package_that_uses_the_services + creating ./python_package_that_uses_the_services/package.xml + creating source folder + creating folder ./python_package_that_uses_the_services/python_package_that_uses_the_services + creating ./python_package_that_uses_the_services/setup.py + creating ./python_package_that_uses_the_services/setup.cfg + creating folder ./python_package_that_uses_the_services/resource + creating ./python_package_that_uses_the_services/resource/python_package_that_uses_the_services + creating ./python_package_that_uses_the_services/python_package_that_uses_the_services/__init__.py + creating folder ./python_package_that_uses_the_services/test + creating ./python_package_that_uses_the_services/test/test_copyright.py + creating ./python_package_that_uses_the_services/test/test_flake8.py + creating ./python_package_that_uses_the_services/test/test_pep257.py + + [WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created. + It is recommended to use one of the ament license identifiers: + Apache-2.0 + BSL-1.0 + BSD-2.0 + BSD-2-Clause + BSD-3-Clause + GPL-3.0-only + LGPL-3.0-only + MIT + MIT-0 + Overview -------- diff --git a/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt b/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt index 10812801..5b12b6ae 100644 --- a/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt +++ b/ros2_tutorial_workspace/src/package_with_interfaces/CMakeLists.txt @@ -17,9 +17,10 @@ find_package(rosidl_default_generators REQUIRED) set(interface_files # Messages "msg/AmazingQuote.msg" + "msg/AmazingQuoteStamped.msg" # Services - "srv/WhatIsThePoint.srv" + "srv/AddPoints.srv" ) diff --git a/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg b/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg new file mode 100644 index 00000000..57e37150 --- /dev/null +++ b/ros2_tutorial_workspace/src/package_with_interfaces/msg/AmazingQuoteStamped.msg @@ -0,0 +1,4 @@ +# AmazingQuoteStamped.msg from https://ros2-tutorial.readthedocs.io +# An AmazingQuote.msg with a header +std_msgs/Header header +AmazingQuote quote \ No newline at end of file diff --git a/ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv b/ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv new file mode 100644 index 00000000..c5ec6dde --- /dev/null +++ b/ros2_tutorial_workspace/src/package_with_interfaces/srv/AddPoints.srv @@ -0,0 +1,6 @@ +# AddPoints.srv from https://ros2-tutorial.readthedocs.io +# Adds the values of points `a` and `b` to give the output `result` +geometry_msgs/Point a +geometry_msgs/Point b +--- +geometry_msgs/Point result \ No newline at end of file diff --git a/ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv b/ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv deleted file mode 100644 index 1fb07b05..00000000 --- a/ros2_tutorial_workspace/src/package_with_interfaces/srv/WhatIsThePoint.srv +++ /dev/null @@ -1,5 +0,0 @@ -# WhatIsThePoint.srv from https://ros2-tutorial.readthedocs.io -# Receives an AmazingQuote and returns what is the point -AmazingQuote quote ---- -geometry_msgs/Point point \ No newline at end of file From 621744fdfd2c9c0962cc242e86dc022aff7be068 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 01:39:13 +0000 Subject: [PATCH 16/25] [python_package_that_uses_the_services] Switching from AmazingQuote to AddPoints. --- .../what_is_the_point_service_client_node.py | 42 +++++------- .../what_is_the_point_service_server_node.py | 64 ++++++------------- 2 files changed, 33 insertions(+), 73 deletions(-) diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py index fea7026c..6f01059e 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py @@ -1,7 +1,7 @@ """ MIT LICENSE -Copyright (C) 2023 Murilo Marques Marinho (www.murilomarinho.info) +Copyright (C) 2023-2025 Murilo Marques Marinho (www.murilomarinho.info) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,18 +28,18 @@ from rclpy.task import Future from rclpy.node import Node -from package_with_interfaces.srv import WhatIsThePoint +from package_with_interfaces.srv import AddPoints -class WhatIsThePointServiceClientNode(Node): - """A ROS2 Node with a Service Client for WhatIsThePoint.""" +class AddPointsServiceClientNode(Node): + """A ROS2 Node with a Service Client for AddPoints.""" def __init__(self): - super().__init__('what_is_the_point_service_client') + super().__init__('add_points_service_client') self.service_client = self.create_client( - srv_type=WhatIsThePoint, - srv_name='/what_is_the_point') + srv_type=AddPoints, + srv_name='/add_points') while not self.service_client.wait_for_service(timeout_sec=1.0): self.get_logger().info(f'service {self.service_client.srv_name} not available, waiting...') @@ -54,19 +54,13 @@ def __init__(self): def timer_callback(self): """Method that is periodically called by the timer.""" - request = WhatIsThePoint.Request() - if random.uniform(0, 1) < 0.5: - request.quote.quote = "I wonder about the Ultimate Question of Life, the Universe, and Everything." - request.quote.philosopher_name = "Creators of Deep Thought" - request.quote.id = 1979 - else: - request.quote.quote = """[...] your living... it is always potatoes. I dream of potatoes.""" - request.quote.philosopher_name = "a young Maltese potato farmer" - request.quote.id = 2013 + request = AddPoints.Request() + request.a = random.uniform(0, 1000) + request.b = random.uniform(0, 1000) if self.future is not None and not self.future.done(): self.future.cancel() # Cancel the future. The callback will be called with Future.result == None. - self.get_logger().info("Service Future canceled. The Node took too long to process the service call." + self.get_logger().warn("Service Future canceled. The Node took too long to process the service call." "Is the Service Server still alive?") self.future = self.service_client.call_async(request) self.future.add_done_callback(self.process_response) @@ -75,15 +69,9 @@ def process_response(self, future: Future): """Callback for the future, that will be called when it is done""" response = future.result() if response is not None: - self.get_logger().info(dedent(f""" - We have thus received the point of our quote. - - {(response.point.x, response.point.y, response.point.z)} - """)) + self.get_logger().info(f"The result was {(response.result.x, response.result.y, response.result.z)}") else: - self.get_logger().info(dedent(""" - The response was None. :( - """)) + self.get_logger().info("The response was None.") def main(args=None): @@ -95,9 +83,9 @@ def main(args=None): try: rclpy.init(args=args) - what_is_the_point_service_client_node = WhatIsThePointServiceClientNode() + add_points_service_client_node = AddPointsServiceClientNode() - rclpy.spin(what_is_the_point_service_client_node) + rclpy.spin(add_points_service_client_node) except KeyboardInterrupt: pass except Exception as e: diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py index 3bc3d4cd..5246a4c7 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py @@ -1,7 +1,7 @@ """ MIT LICENSE -Copyright (C) 2023 Murilo Marques Marinho (www.murilomarinho.info) +Copyright (C) 2023-25 Murilo Marques Marinho (www.murilomarinho.info) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,61 +26,33 @@ import rclpy from rclpy.node import Node -from package_with_interfaces.srv import WhatIsThePoint +from package_with_interfaces.srv import AddPoints -class WhatIsThePointServiceServerNode(Node): - """A ROS2 Node with a Service Server for WhatIsThePoint.""" +class AddPointsServiceServerNode(Node): + """A ROS2 Node with a Service Server for AddPoints.""" def __init__(self): super().__init__('what_is_the_point_service_server') self.service_server = self.create_service( - srv_type=WhatIsThePoint, - srv_name='/what_is_the_point', - callback=self.what_is_the_point_service_callback) + srv_type=AddPoints, + srv_name='/add_points', + callback=self.add_points_service_callback) self.service_server_call_count: int = 0 - def what_is_the_point_service_callback(self, - request: WhatIsThePoint.Request, - response: WhatIsThePoint.Response - ) -> WhatIsThePoint.Response: - """Analyses an AmazingQuote and returns what is the point. - If the quote contains 'life', it returns a point whose sum of coordinates is 42. - Otherwise, it returns a random point whose sum of coordinates is not 42. + def add_points_service_callback(self, + request: AddPoints.Request, + response: AddPoints.Response + ) -> AddPoints.Response: + """ + Adds the two points `a` and `b` in the request and returns the `result`. """ - # Generate the x,y,z of the point - if "life" in request.quote.quote.lower(): - x: float = random.uniform(0, 42) - y: float = random.uniform(0, 42 - x) - z: float = 42 - (x + y) - else: - x: float = random.uniform(0, 100) - y: float = random.uniform(0, 100) - z: float = random.uniform(0, 100) - if x + y + z == 42: # So you’re telling me there’s a chance? Yes! - x = x + 1 # Not anymore :( - - # Assign to the response - response.point.x = x - response.point.y = y - response.point.z = z - - # Increase the call count - self.service_server_call_count = self.service_server_call_count + 1 - - self.get_logger().info(dedent(f""" - This is the call number {self.service_server_call_count} to this Service Server. - The analysis of the AmazingQuote below is complete. - - {request.quote.quote} - - -- {request.quote.philosopher_name} - - The point has been sent back to the client. - """)) + response.result.x = request.a.x + request.b.x + response.result.y = request.a.y + request.b.y + response.result.z = request.a.z + request.b.z return response @@ -94,9 +66,9 @@ def main(args=None): try: rclpy.init(args=args) - what_is_the_point_service_server_node = WhatIsThePointServiceServerNode() + add_points_service_server_node = AddPointsServiceServerNode() - rclpy.spin(what_is_the_point_service_server_node) + rclpy.spin(add_points_service_server_node) except KeyboardInterrupt: pass except Exception as e: From 4ea1160e306c9fb259d22cb374896ce7017bee61 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 01:43:25 +0000 Subject: [PATCH 17/25] [python_package_that_uses_the_services] Fixing node names and syntax. --- ...lient_node.py => add_points_service_client_node.py} | 10 ++++++++-- ...erver_node.py => add_points_service_server_node.py} | 0 .../src/python_package_that_uses_the_services/setup.py | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) rename ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/{what_is_the_point_service_client_node.py => add_points_service_client_node.py} (92%) rename ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/{what_is_the_point_service_server_node.py => add_points_service_server_node.py} (100%) diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py similarity index 92% rename from ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py rename to ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py index 6f01059e..a8f23c6c 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py @@ -55,8 +55,14 @@ def timer_callback(self): """Method that is periodically called by the timer.""" request = AddPoints.Request() - request.a = random.uniform(0, 1000) - request.b = random.uniform(0, 1000) + + request.a.x = random.uniform(0, 1000) + request.a.y = random.uniform(0, 1000) + request.a.y = random.uniform(0, 1000) + + request.b.x = random.uniform(0, 1000) + request.b.y = random.uniform(0, 1000) + request.b.z = random.uniform(0, 1000) if self.future is not None and not self.future.done(): self.future.cancel() # Cancel the future. The callback will be called with Future.result == None. diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py similarity index 100% rename from ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py rename to ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/setup.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/setup.py index 316a7fec..09e7d6c9 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/setup.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/setup.py @@ -20,10 +20,10 @@ tests_require=['pytest'], entry_points={ 'console_scripts': [ - 'what_is_the_point_service_client_node = ' - 'python_package_that_uses_the_services.what_is_the_point_service_client_node:main', - 'what_is_the_point_service_server_node = ' - 'python_package_that_uses_the_services.what_is_the_point_service_server_node:main' + 'add_points_service_client_node = ' + 'python_package_that_uses_the_services.add_points_service_client_node:main', + 'add_points_service_server_node = ' + 'python_package_that_uses_the_services.add_points_service_server_node:main' ], }, ) From df7d5d5539fec2a5fb4a34378ab34438f645952e Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 01:51:23 +0000 Subject: [PATCH 18/25] [service_servers_and_clients.rst] Updated example to use AddPoints. --- docs/source/service_servers_and_clients.rst | 123 +++++++------------- 1 file changed, 41 insertions(+), 82 deletions(-) diff --git a/docs/source/service_servers_and_clients.rst b/docs/source/service_servers_and_clients.rst index 6c89c592..81131b30 100644 --- a/docs/source/service_servers_and_clients.rst +++ b/docs/source/service_servers_and_clients.rst @@ -89,30 +89,30 @@ Create the Node with a Service Server #. Add the new Node to :file:`setup.py` -Let's start by creating a :file:`what_is_the_point_service_server_node.py` in :file:`~/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services` with the following contents +Let's start by creating a :file:`add_points_service_server_node.py` in :file:`~/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services` with the following contents -:download:`what_is_the_point_service_server_node.py <../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py>` +:download:`add_points_service_server_node.py <../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py>` -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python :lines: 24- The code begins with an import to the service we created. No surprise here. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python :lines: 24-29 :emphasize-lines: 6 The Service Server must be initialised with the :code:`create_service()`, as follows, with parameters that should by now be quite obvious to us. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python :lines: 38-41 -The Service Server receives a :code:`WhatIsThePoint.Request` and returns a :code:`WhatIsThePoint.Response`. +The Service Server receives a :code:`AddPoints.Request` and returns a :code:`AddPoints.Response`. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python :lines: 45-52 :emphasize-lines: 2,4 @@ -122,18 +122,17 @@ The Service Server receives a :code:`WhatIsThePoint.Request` and returns a :code The API for the Service Server callback is a bit weird in that needs the :code:`Response` as an argument. This API `might change `_, but for now this is what we got. -We play around with the :code:`WhatIsThePoint.Request` a bit and use that result to populate a :code:`WhatIsThePoint.Response`, as follows +We use the members of :code:`AddPoints.Request` to calculate and populate the :code:`AddPoints.Response`, as follows -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python - :lines: 66-69 + :lines: 53-55 -At the end of the callback, we must return that :code:`WhatIsThePoint.Request`, like so +At the end of the callback, we must return that :code:`AddPoints.Request`, like so -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_server_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py :language: python - :lines: 85 - :emphasize-lines: 2 + :lines: 57 The Service Server was quite painless, but it doesn't do much. The Service Client might be a bit more on the painful side for the uninitiated. @@ -167,11 +166,11 @@ The Node This implementation shown herein uses a callback and :code:`rclpy.spin()`. It has many practical applications, but it's no *panacea*. -We start by adding a :file:`what_is_the_point_service_client_node.py` at :file:`python_package_that_uses_the_services/python_package_that_uses_the_services` with the following contents. +We start by adding a :file:`add_points_service_client_node.py` at :file:`python_package_that_uses_the_services/python_package_that_uses_the_services` with the following contents. -:download:`what_is_the_point_service_client_node.py <../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py>` +:download:`add_points_service_client_node.py <../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py>` -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :linenos: :lines: 24- @@ -181,7 +180,7 @@ Imports To have access to the service, we import it with :code:`from .srv import `. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 31 @@ -190,7 +189,7 @@ Instantiate a Service Client We instantiate a Service Client with :code:`Node.create_client()`. The values of :code:`srv_type` and :code:`srv_name` must match the ones used in the Service Server. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 40-42 @@ -202,7 +201,7 @@ We instantiate a Service Client with :code:`Node.create_client()`. The values of In many cases, having the result of the service is of particular importance (hence the use of a service and not messages). In that case, we have to wait until :code:`service_client.wait_for_service()`, as shown below. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 44,45 @@ -211,7 +210,7 @@ Instantiate a :code:`Future` as a class attribute As part of the :code:`async` framework, we instantiate a :code:`Future` (`More info `_). In this example it is important to have it as an attribute of the class so that we do not lose the reference to it after the callback. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 47 @@ -220,13 +219,13 @@ Instantiate a Timer Whenever periodic work must be done, it is recommended to use a :code:`Timer`, as we already learned in :ref:`Use a Timer for periodic work`. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 49-52 The need for a callback for the :code:`Timer`, should also be no surprise. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 54-55 @@ -235,7 +234,7 @@ Service Clients use :code:`.Request()` Given that services work in a request-response model, the Service Client must instantiate a suitable :code:`.Request()` and populate its fields before making the service call, as shown below. To make the example more interesting, it randomly switches between two possible quotes. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 57-65 @@ -254,14 +253,14 @@ There are many ways to address the use of a :code:`Future`. One of them, special The benefit of this is that the callback will not block our resources until the response is ready. When the response is ready, and the ROS2 executor gets to processing :code:`Future` callbacks, our callback will be called *automagically*. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 67-72 :emphasize-lines: 5,6 Given that we are periodically calling the service, before replace the class :code:`Future` with the next service call, we can check if the service call was done with :code:`Future.done()`. If it is not done, we can use :code:`Future.cancel()` so that our callback can handle this case as well. For instance, if the Service Server has been shutdown, the :code:`Future` would never be done. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 67-72 :emphasize-lines: 1-4 @@ -273,7 +272,7 @@ The callback for the :code:`Future` must receive a :code:`Future` as an argument The result of the :code:`Future` is obtained using :code:`Future.result()`. The response might be :code:`None` in some cases, so we must check it before trying to use the result, otherwise we will get a nasty exception. -.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/what_is_the_point_service_client_node.py +.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_client_node.py :language: python :lines: 74-88 :emphasize-lines: 1,3,4 @@ -302,73 +301,33 @@ Testing Service Server and Client .. code:: console - ros2 run python_package_that_uses_the_services what_is_the_point_service_client_node + ros2 run python_package_that_uses_the_services add_points_service_client_node when running the client Node, the server is still not active. In that case, the client node will keep waiting for it, as follows .. code:: console - [INFO] [1684293008.888276849] [what_is_the_point_service_client]: service /what_is_the_point not available, waiting... - [INFO] [1684293009.890589539] [what_is_the_point_service_client]: service /what_is_the_point not available, waiting... - [INFO] [1684293010.892778194] [what_is_the_point_service_client]: service /what_is_the_point not available, waiting... + [INFO] [1753667386.959416097] [add_points_service_client]: service /add_points not available, waiting... + [INFO] [1753667387.967904375] [add_points_service_client]: service /add_points not available, waiting... + [INFO] [1753667388.978200250] [add_points_service_client]: service /add_points not available, waiting... -In another terminal, we run the :program:`what_is_the_point_service_server_node`, as follows +In another terminal, we run the :program:`add_points_service_server_node`, as follows .. code:: console - ros2 run python_package_that_uses_the_services what_is_the_point_service_server_node + ros2 run python_package_that_uses_the_services add_points_service_server_node -The server Node will then output, periodically, +The server Node will output nothing, whereas the client Node will output, periodically, .. code:: console - [INFO] [1684485151.608507798] [what_is_the_point_service_server]: - This is the call number 1 to this Service Server. - The analysis of the AmazingQuote below is complete. - - [...] your living... it is always potatoes. I dream of potatoes. - - -- a young Maltese potato farmer - - The point has been sent back to the client. - - [INFO] [1684485152.092508332] [what_is_the_point_service_server]: - This is the call number 2 to this Service Server. - The analysis of the AmazingQuote below is complete. - - I wonder about the Ultimate Question of Life, the Universe, and Everything. - - -- Creators of Deep Thought - - The point has been sent back to the client. - - [INFO] [1684485152.592516148] [what_is_the_point_service_server]: - This is the call number 3 to this Service Server. - The analysis of the AmazingQuote below is complete. - - I wonder about the Ultimate Question of Life, the Universe, and Everything. - - -- Creators of Deep Thought - - The point has been sent back to the client. - -and the client Node will output, periodically, - -.. code:: console - - [INFO] [1684485151.609611689] [what_is_the_point_service_client]: - We have thus received the point of our quote. - - (18.199457100225292, 33.14595477433704, 52.65262570058381) - - [INFO] [1684485152.093228181] [what_is_the_point_service_client]: - We have thus received the point of our quote. - - (11.17170193214362, 9.384897014549527, 21.443401053306854) - - [INFO] [1684485152.593294259] [what_is_the_point_service_client]: - We have thus received the point of our quote. - - (16.58535176162403, 0.6180505400411676, 24.796597698334804) + [INFO] [1753667415.876223138] [add_points_service_client]: The result was (853.122385593111, 613.3399959983066, 722.6376752208978) + [INFO] [1753667416.373657638] [add_points_service_client]: The result was (645.418992882397, 560.9466217293334, 874.7214190239486) + [INFO] [1753667416.875945305] [add_points_service_client]: The result was (1270.7448356640075, 345.69676803639936, 953.6879012399689) + [INFO] [1753667417.376013639] [add_points_service_client]: The result was (1203.944887411107, 733.5131783020975, 927.902266740569) + [INFO] [1753667417.872921291] [add_points_service_client]: The result was (671.9458297091917, 1210.490009902154, 545.6078440547075) + [INFO] [1753667418.371451541] [add_points_service_client]: The result was (629.0766519110047, 872.0699525880541, 581.7396957576223) + [INFO] [1753667418.873213958] [add_points_service_client]: The result was (579.0485532702639, 1714.0365146695003, 396.1743388215037) + [INFO] [1753667419.375147834] [add_points_service_client]: The result was (545.2849740451343, 1629.1832720438556, 945.1871456532875) From 1932850267d26b2aa53bd113ade4a7b8ae0105f7 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 01:57:47 +0000 Subject: [PATCH 19/25] [inspecting_services.rst] Update to use AddPoints.srv --- docs/source/inspecting_services.rst | 59 ++++++++++--------- .../add_points_service_server_node.py | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/source/inspecting_services.rst b/docs/source/inspecting_services.rst index 063385ec..e732530c 100644 --- a/docs/source/inspecting_services.rst +++ b/docs/source/inspecting_services.rst @@ -1,7 +1,7 @@ Inspecting services (:program:`ros2 service`) ============================================= -ROS2 has a tool to help us inspect services. It is just as helpful as the tools for topics. +:program:`ROS2` has a tool to help us inspect services. It is just as helpful as the tools for topics. .. code:: console @@ -12,23 +12,23 @@ which outputs the detailed information of the tool, as shown below. In particula .. code-block:: console :emphasize-lines: 13,15 - usage: ros2 service [-h] [--include-hidden-services] - Call `ros2 service -h` for more - detailed usage. ... - + usage: ros2 service [-h] [--include-hidden-services] Call `ros2 service -h` for more detailed usage. ... + Various service related sub-commands - + options: -h, --help show this help message and exit --include-hidden-services Consider hidden services as well - + Commands: call Call a service + echo Echo a service find Output a list of available services of a given type + info Print information about a service list Output a list of available services type Output a service's type - + Call `ros2 service -h` for more detailed usage. Start a service server @@ -42,7 +42,7 @@ Similar to the discussion about topics, it is good to be able to test service se .. code:: console - ros2 run python_package_that_uses_the_services what_is_the_point_service_server_node + ros2 run python_package_that_uses_the_services add_points_service_server_node Getting all services with :program:`ros2 service list` ------------------------------------------------------ @@ -58,13 +58,14 @@ which, in this case, outputs .. code-block:: console :emphasize-lines: 1 - /what_is_the_point - /what_is_the_point_service_server/describe_parameters - /what_is_the_point_service_server/get_parameter_types - /what_is_the_point_service_server/get_parameters - /what_is_the_point_service_server/list_parameters - /what_is_the_point_service_server/set_parameters - /what_is_the_point_service_server/set_parameters_atomically + /add_points + /add_points_service_server/describe_parameters + /add_points_service_server/get_parameter_types + /add_points_service_server/get_parameters + /add_points_service_server/get_type_description + /add_points_service_server/list_parameters + /add_points_service_server/set_parameters + /add_points_service_server/set_parameters_atomically To everyone's surprise, there are a lot of services beyond the one we created. We can address those when we talk about ROS2 parameters, for now, we ignore them. @@ -76,25 +77,29 @@ Back to our example, we can do .. code-block:: console - ros2 service call /what_is_the_point \ - package_with_interfaces/srv/WhatIsThePoint \ + ros2 service call /add_points \ + package_with_interfaces/srv/AddPoints \ '{ - quote: { - id: 1994, - quote: So you’re telling me there’s a chance, - philosopher_name: Lloyd - } + a: { + x: 10, + y: 11, + z: 12 + }, + b: { + x: -10, + y: -10, + z: 22 + } }' which results in .. code-block:: console - waiting for service to become available... - requester: making request: package_with_interfaces.srv.WhatIsThePoint_Request(quote=package_with_interfaces.msg.AmazingQuote(id=1994, quote='So you’re telling me there’s a chance', philosopher_name='Lloyd')) - + requester: making request: package_with_interfaces.srv.AddPoints_Request(a=geometry_msgs.msg.Point(x=10.0, y=11.0, z=12.0), b=geometry_msgs.msg.Point(x=-10.0, y=-10.0, z=22.0)) + response: - package_with_interfaces.srv.WhatIsThePoint_Response(point=geometry_msgs.msg.Point(x=8.327048266159165, y=95.97987946924988, z=67.03878311627777)) + package_with_interfaces.srv.AddPoints_Response(result=geometry_msgs.msg.Point(x=0.0, y=1.0, z=34.0)) Testing your service clients??? ------------------------------- diff --git a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py index 5246a4c7..fca79648 100644 --- a/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py +++ b/ros2_tutorial_workspace/src/python_package_that_uses_the_services/python_package_that_uses_the_services/add_points_service_server_node.py @@ -33,7 +33,7 @@ class AddPointsServiceServerNode(Node): """A ROS2 Node with a Service Server for AddPoints.""" def __init__(self): - super().__init__('what_is_the_point_service_server') + super().__init__('add_points_service_server') self.service_server = self.create_service( srv_type=AddPoints, From 436759fa90d6e49106ad08852b7dc2c7d636f493 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 02:02:49 +0000 Subject: [PATCH 20/25] [parameters] Adjusted parameter-related files to Jazzy. --- docs/source/inspecting_parameters.rst | 12 +----------- docs/source/launch_configurable_nodes.rst | 2 -- docs/source/parameters_and_launch.rst | 2 -- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/docs/source/inspecting_parameters.rst b/docs/source/inspecting_parameters.rst index a23f5694..cde271a6 100644 --- a/docs/source/inspecting_parameters.rst +++ b/docs/source/inspecting_parameters.rst @@ -1,5 +1,3 @@ -.. include:: the_topic_is_under_heavy_construction.rst - Inspecting parameters (:program:`ros2 param`) ============================================= @@ -26,7 +24,7 @@ which returns Commands: delete Delete parameter describe Show descriptive information about declared parameters - dump Dump the parameters of a node to a yaml file + dump Show all of the parameters of a node in a YAML file format get Get parameter list Output a list of available parameters load Load parameter file for a node @@ -34,10 +32,6 @@ which returns Call `ros2 param -h` for more detailed usage. -.. note:: - - By the time you try this out, the documentation of :program:`ros2 param dump` might have changed. See `ros2/ros2cli/#835 `_. - As shown in the emphasized lines above, the :program:`ros2 param` tool has a large number of useful commands to interact with parameters. Launching the Node with parameters @@ -161,10 +155,6 @@ Changing parameters is not instantaneous and, after the change becomes visible i Save parameters to a file with :program:`ros2 param dump` --------------------------------------------------------- -.. warning:: - - At the time I was writing this part of the tutorial, `the description `_ of :program:`ros2 param dump` was outdated. By the time you try this out, it might have been corrected. See `ros2/ros2cli/#836 `_ for more info. - Words are sometimes little happy accidents. This usage of the word dump has no relation whatsoever to, for example, `Peter got dumped by Sarah and went to Hawaii `_. Dump files are usually related to `crashes and unresponsive programs `_, so this name puzzles me since ROS: the first. While we wait for someone to come and correct me on my claims above, just think about this as a weird name for :program:`ros2 param print_to_screen_as_yaml`. It prints the parameters in the terminal with a YAML file format. It is nice because it gives a bit more info than :program:`ros2 param list`, but not so useful as-is. The trick is that we can put all that nicely formatted content into a file with diff --git a/docs/source/launch_configurable_nodes.rst b/docs/source/launch_configurable_nodes.rst index 77138061..44936564 100644 --- a/docs/source/launch_configurable_nodes.rst +++ b/docs/source/launch_configurable_nodes.rst @@ -1,5 +1,3 @@ -.. include:: the_topic_is_under_heavy_construction.rst - Launch configurable Nodes (:program:`ros2 launch`) -------------------------------------------------- diff --git a/docs/source/parameters_and_launch.rst b/docs/source/parameters_and_launch.rst index 53c28845..ea1c1f04 100644 --- a/docs/source/parameters_and_launch.rst +++ b/docs/source/parameters_and_launch.rst @@ -1,5 +1,3 @@ -.. include:: the_topic_is_under_heavy_construction.rst - Parameters: creating configurable Nodes ======================================= From 01af02bb6a1de23a9c3323a100c43cf742dc74d9 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 04:18:41 +0000 Subject: [PATCH 21/25] [installing_python.rst] Updating to Ubuntu 24.04 versions. --- .../preamble/python/installing_python.rst | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/source/preamble/python/installing_python.rst b/docs/source/preamble/python/installing_python.rst index e8ae374c..6b1f0ba4 100644 --- a/docs/source/preamble/python/installing_python.rst +++ b/docs/source/preamble/python/installing_python.rst @@ -7,7 +7,7 @@ Installing Python on Ubuntu If you change or try to tinker with the default Python version of Ubuntu, your system will most likely **BREAK COMPLETELY**. Do not play around with the default Python installation, because Ubuntu depends on it to work properly (or work at all). -In Ubuntu 22.04, Python is already installed! +In Ubuntu, Python is already installed! In fact, Ubuntu would not work without it. Let's check its version by running .. code-block:: console @@ -18,9 +18,9 @@ which should output .. code-block:: console - Python 3.10.6 + Python 3.12.3 -If the :code:`3.10` part of your version is different (e.g. :code:`3.9` or :code:`3.11`\), get this fixed because this tutorial will not work for you. +If the :code:`3.12` part of your version is different, this tutorial might not work for you. Please make sure to use the default Python in your Ubuntu. .. warning:: Note that the command is :program:`python3` and not :program:`python`. In fact, the result of @@ -50,13 +50,13 @@ which should output something similar to .. code-block:: console - Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux + Python 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> -in particular, if the :code:`GCC 11` is different, e.g. :code:`GCC 9` or :code:`GCC 12`\, then get this fixed because this tutorial will not work for you. +in particular, if the :code:`GCC 13` is different, then this tutorial might not work for you. -As you already know, to exit the `interactive shell `_ you can use :kbd:`CTRL+D` or type :code:`quit()` and press :kbd:`ENTER`. +As you already know, to exit the `interactive shell `_ you can use :kbd:`CTRL+D` or type :code:`quit()` and press :kbd:`ENTER`. Some Python packages must be installed through :program:`apt` ------------------------------------------------------------- @@ -83,7 +83,7 @@ When you want to isolate your environment, use :program:`venv` Until that is handled, we are not going to use :program:`venv` for the ROS2 tutorials. However, we will use :program:`venv` to protect our ROS2 environment from these Python preamble tutorials. -Using :program:`venv` (`More info `_) is quite straightforward. +Using :program:`venv` (`More info `_) is quite straightforward. Create a :file:`venv` ^^^^^^^^^^^^^^^^^^^^^ @@ -151,20 +151,20 @@ then, we install .. code-block:: console - python3 -m pip install dqrobotics + python3 -m pip install dqrobotics --break-system-packages which will result in something similar to (might change depending on future versions) .. code-block:: console - Collecting dqrobotics - Downloading dqrobotics-23.4.0a15-cp310-cp310-manylinux1_x86_64.whl (551 kB) - ---------------------------------------- 551.4/551.4 KB 6.3 MB/s eta 0:00:00 - Collecting numpy - Downloading numpy-1.25.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.6 MB) - ---------------------------------------- 17.6/17.6 MB 7.4 MB/s eta 0:00:00 - Installing collected packages: numpy, dqrobotics - Successfully installed dqrobotics-23.4.0a15 numpy-1.25.0 + Collecting dqrobotics + Downloading dqrobotics-25.4.0a17-cp312-cp312-manylinux2014_aarch64.whl.metadata (2.9 kB) + Requirement already satisfied: numpy in /usr/lib/python3/dist-packages (from dqrobotics) (1.26.4) + Downloading dqrobotics-25.4.0a17-cp312-cp312-manylinux2014_aarch64.whl (512 kB) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 512.5/512.5 kB 14.0 MB/s eta 0:00:00 + [...] + Installing collected packages: dqrobotics + Successfully installed dqrobotics-25.4.0a17 Removing libraries (installed with :program:`pip`) -------------------------------------------------- @@ -173,18 +173,18 @@ We can remove the library we just installed with .. code-block:: console - python3 -m pip uninstall dqrobotics + python3 -m pip uninstall dqrobotics --break-system-packages resulting in .. code-block:: console - Found existing installation: dqrobotics 23.4.0a15 - Uninstalling dqrobotics-23.4.0a15: - Would remove: - /home/murilo/ros2tutorial_venv/lib/python3.10/site-packages/dqrobotics-23.4.0a15.dist-info/* - /home/murilo/ros2tutorial_venv/lib/python3.10/site-packages/dqrobotics/* - Proceed (Y/n)? + Found existing installation: dqrobotics 25.4.0a7 + Uninstalling dqrobotics-25.4.0a7: + Would remove: + /usr/local/lib/python3.12/dist-packages/dqrobotics-25.4.0a7.dist-info/* + /usr/local/lib/python3.12/dist-packages/dqrobotics/* + Proceed (Y/n)? .. hint:: @@ -195,7 +195,7 @@ Then, press :kbd:`ENTER`, which results in .. code-block:: console - Successfully uninstalled dqrobotics-23.4.0a15 + Successfully uninstalled dqrobotics-25.4.0a7 When using :program:`pip`, do **NOT** use :code:`sudo` ------------------------------------------------------ From e41d2562c33db04aa13519621024803b17481336 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 04:21:10 +0000 Subject: [PATCH 22/25] [conf.py] Updating announcement for clarify. --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index c8d12b61..3cd664ac 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,8 +48,8 @@ html_theme = 'sphinx_book_theme' # Tried with `furo` on May 23, 2025, but somehow it didn't look right. In particular the download button wasn't as clear. html_theme_options = { - "announcement": "The documentation is being updated to Jazzy in this branch. " - "See Humble Docs for the stable ones. " + "announcement": "This tutorial has been updated to ROS2 Jazzy. " + "See Humble Docs for the previous version. " "Create an issue for inconsistencies.", } html_title = project From f7b293fbd001b366963c4139f5c9084fbd8dd487 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 04:54:55 +0000 Subject: [PATCH 23/25] [publishers_and_subscribers.rst] Fixing warnings --- docs/source/conf.py | 2 +- docs/source/publishers_and_subscribers.rst | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3cd664ac..eaf41a10 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,7 +36,7 @@ ] intersphinx_mapping = { - 'python': ('https://docs.python.org/3.10/', None), + 'python': ('https://docs.python.org/3.12/', None), 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), } intersphinx_disabled_domains = ['std'] diff --git a/docs/source/publishers_and_subscribers.rst b/docs/source/publishers_and_subscribers.rst index a2edc87a..6d9ee4f2 100644 --- a/docs/source/publishers_and_subscribers.rst +++ b/docs/source/publishers_and_subscribers.rst @@ -136,14 +136,14 @@ The publisher must be created with the :code:`Node.create_publisher(...)` method .. _publisher and subscriber parameter table: -+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -|:code:`msg_type` | A class, namely the message that will be used in the topic. In this case, :code:`AmazingQuote`. | -+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -|:code:`topic` | The topic through which the communication will occur. Can be arbitrarily chosen, but to make sense :code:`/amazing_quote`. | -+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ -|:code:`qos_profile` | The simplest interpretation for this parameter is the maximum number of messages that will be stored in a buffer if your node (including :code:`spin(...)`) takes too long to process them. | -| | (See more on `docs for QoSProfile `_.) | -+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------+ ++--------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|:code:`msg_type` | A class, namely the message that will be used in the topic. In this case, :code:`AmazingQuote`. | ++--------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|:code:`topic` | The topic through which the communication will occur. Can be arbitrarily chosen, but to make sense :code:`/amazing_quote`. | ++--------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|:code:`qos_profile` | The simplest interpretation for this parameter is the maximum number of messages that will be stored in a buffer if your node (including :code:`spin(...)`) takes too long to process them. | +| | (See more on `docs for QoSProfile `_.) | ++--------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. warning:: From 76a84a5cb55255d4e834894daa1bf2f08d6f07c5 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 09:21:55 +0000 Subject: [PATCH 24/25] [publishers_and_subscribers] Fixing id reference. --- docs/source/publishers_and_subscribers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/publishers_and_subscribers.rst b/docs/source/publishers_and_subscribers.rst index 6d9ee4f2..d45bbd67 100644 --- a/docs/source/publishers_and_subscribers.rst +++ b/docs/source/publishers_and_subscribers.rst @@ -292,6 +292,6 @@ which outputs .. warning:: - Unless instructed otherwise, the publisher does **NOT** wait for a subscriber to connect before it starts publishing the messages. As shown in the case above, the first message we received started with `id=3`. If we delayed longer to start the publisher, we would have received later messages only. + Unless instructed otherwise, the publisher does **NOT** wait for a subscriber to connect before it starts publishing the messages. As shown in the case above, the first message we received started with `id>0`. If we delayed longer to start the publisher, we would have received later messages only. Let's close each node with :kbd:`CTRL+C` on each terminal before we proceed to the next tutorial. From 64173dee8c6d543e3413c7ef69b480e39cfcf915 Mon Sep 17 00:00:00 2001 From: Murilo Marinho Date: Mon, 28 Jul 2025 09:26:34 +0000 Subject: [PATCH 25/25] [puppeteer-config.json] Fixed spelling. --- docs/source/{peppeteer-config.json => puppeteer-config.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/source/{peppeteer-config.json => puppeteer-config.json} (100%) diff --git a/docs/source/peppeteer-config.json b/docs/source/puppeteer-config.json similarity index 100% rename from docs/source/peppeteer-config.json rename to docs/source/puppeteer-config.json