Conventions
ROS packages
ROS packages are defined in the ros2_ws/src directory.
Folder Structure
We try to follow this folder structure for the ROS packages:
ros_package/
| CMakeLists.txt
| package.xml
|
└───src/
| | cpp_node.cpp
|
└───include/
| └───ros_package/
| | cpp_header.hpp
|
└───src_py/
| | py_node.py
|
└───ros_package/
| | __init__.py
| | py_module.py
│ └── test/
│ ├── conftest.py
│ ├── unit/
│ │ └── test_py_module.py
│ ├── integration/
│ │ └── test_service_client.py
│ └── end_to_end/
│ └── test_full_launch.py
└───launch/
| launch_file.launch.py
Every ROS package contains the
CMakeLists.txtandpackage.xmlin the root directory.In case of development using C++, the executables are located in the
src/directory. The corresponding headers are located in theinclude/directory, inside a sub-directory that equals the package name.In case of development using Python, the executables are located in the
src_py/directory.A sub-directory
ros_package/with the same name as the ROS package can be used to create a Python package. This directory contains an__init__.pyfile and the Python modules of the Python package.Possible launch files are located in the
launch/directory.More directories are possible, like
urdf/for urdf files orconfig/for config files.The testing layout is placed alongside the Python package, with these subfolders:
unit/for fast, logic-only testsintegration/for multi-component or ROS client/server testsend_to_end/for full launch/simulation tests
Use a top-level
test/conftest.pyto define shared fixtures, and name your files/functionstest_*so pytest auto-discovers them. The most general fixtures are defined in theconftest.pyin the root of the repository.
CMakeLists.txt
This CMakeLists.txt file shows the different parts required to build a ROS package:
1# SPDX-FileCopyrightText: Alliander N. V.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5cmake_minimum_required(VERSION 3.5)
6project(ros_package)
7
8# CMake dependencies:
9find_package(ament_cmake REQUIRED)
10find_package(ament_cmake_python REQUIRED)
11
12# Other dependencies:
13find_package(geometry_msgs REQUIRED)
14find_package(vision_msgs REQUIRED)
15
16# C++ executables:
17add_executable(cpp_node src/cpp_node.cpp)
18ament_target_dependencies(cpp_node geometry_msgs vision_msgs)
19install(
20 TARGETS cpp_node
21 DESTINATION lib/${PROJECT_NAME}
22)
23
24# Python executables:
25install(
26 DIRECTORY src_py/
27 DESTINATION lib/${PROJECT_NAME}
28)
29
30# Python package:
31ament_python_install_package(${PROJECT_NAME})
32
33# Shared folders:
34install(
35 DIRECTORY launch
36 DESTINATION share/${PROJECT_NAME}
37)
38
39# Default test:
40if(BUILD_TESTING)
41 find_package(ament_lint_auto REQUIRED)
42 ament_lint_auto_find_test_dependencies()
43endif()
44
45ament_package()
5-10:
The file always starts with a version definition, the package name and the CMake dependencies when building C++ and/or python files.
12-14:
If the package depends on other packages, these are defined. In this case, the packaged depends on the vision_msgs and geometry_msgs packages.
16-22:
Building a C++ executable requires 3 steps: defining the executable, linking dependencies (if any) and installing the targets to the lib directory.
24-28:
For Python executables, we can simply install them all at the same time, by providing the directory.
30-31:
If the package contains a Python package, it needs to be installed.
33-37:
All shared folders are installed into the share directory. This includes the directory of launch files, but also other possible directories, like urdf/ or config/, if these exist.
39-45:
The file always ends with a default test and the ament_package() command.
package.xml
The package.xml file is related to the CMakeLists.txt file:
1<?xml version="1.0"?>
2<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3
4<!--
5SPDX-FileCopyrightText: Alliander N. V.
6
7SPDX-License-Identifier: Apache-2.0
8-->
9
10<package format="3">
11 <name>ros_package</name>
12 <version>0.1.0</version>
13 <description>A ros package.</description>
14 <maintainer email="researchcenter@alliander.com">RCDT</maintainer>
15 <license>Apache 2.0</license>
16
17 <buildtool_depend>ament_cmake</buildtool_depend>
18 <buildtool_depend>ament_cmake_python</buildtool_depend>
19
20 <depend>geometry_msgs</depend>
21 <depend>vision_msgs</depend>
22
23 <test_depend>ament_lint_auto</test_depend>
24
25 <export>
26 <build_type>ament_cmake</build_type>
27 </export>
28</package>
1-2:
The files starts with default xml definitions.
10-15:
Inside the package tag, we start with some general information about the package.
17-18:
Next, we define the build tool dependencies for building C++ and/or Python files.
20-21:
Next, we define other packages where our package depends on.
23-28:
The file ends with the default test dependency and an export definition.
Custom Messages, Services and Actions
We define custom messages, services and actions in our rcdt_interfaces package. This package has the following folder structure:
ros_package/
| CMakeLists.txt
| package.xml
|
└───msg/
| | custom_message_definition.msg
|
└───srv/
| | custom_service_definition.srv
|
└───action/
| custom_action_definition.action
In the CMake file, we automatically look for all msg, srv and action files and generate interfaces for them:
1# SPDX-FileCopyrightText: Alliander N. V.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5cmake_minimum_required(VERSION 3.5)
6project(ros_package)
7
8# CMake dependencies:
9find_package(ament_cmake REQUIRED)
10find_package(rosidl_default_generators REQUIRED)
11
12# Other dependencies:
13find_package(geometry_msgs REQUIRED)
14find_package(vision_msgs REQUIRED)
15
16file(GLOB MSGS CONFIGURE_DEPENDS msg/*.msg*)
17file(GLOB SRVS CONFIGURE_DEPENDS srv/*.srv*)
18file(GLOB ACTIONS CONFIGURE_DEPENDS action/*.action*)
19
20foreach(file IN LISTS MSGS)
21 string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" file_relative ${file})
22 list(APPEND MSGS_STRIPPED ${file_relative})
23endforeach()
24
25foreach(file IN LISTS SRVS)
26 string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" file_relative ${file})
27 list(APPEND SRVS_STRIPPED ${file_relative})
28endforeach()
29
30foreach(file IN LISTS ACTIONS)
31 string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" file_relative ${file})
32 list(APPEND ACTIONS_STRIPPED ${file_relative})
33endforeach()
34
35rosidl_generate_interfaces(${PROJECT_NAME}
36 ${MSGS_STRIPPED}
37 ${SRVS_STRIPPED}
38 ${ACTIONS_STRIPPED}
39 DEPENDENCIES geometry_msgs vision_msgs
40)
41
42# Default test:
43if(BUILD_TESTING)
44 find_package(ament_lint_auto REQUIRED)
45 ament_lint_auto_find_test_dependencies()
46endif()
47
48ament_package()
To generate custom messages successfully, we also need to specify the following dependencies in the package.xml file:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
The Service Structure
Unless more data is required, the Response of our services contains the following data by default:
---
bool success
string message
When our service only requires a single request datatype, and the default response as described above, the name of the service will simply be the datatype of the request + Srv. So for example, when the only datatype in the request is a string, the service will be called StringSrv.srv.
In case the service is more complex, the name of the service will represent its purpose. E.g., if the intention of the service is to add an object to a scene, the name of the service will be AddObject.srv.
The Action Structure
Unless more data is required, the Response and Feedback of our actions contains the following data by default:
---
bool success
string message
---
string status
When our action only requires a single request datatype, and the default response & feedback as described above, the name of the action will simply be the datatype of the request + Action. So for example, when the only datatype in the request is a string, the action will be called StringAction.action.
In case the action is more complex, the name of the action will represent its purpose. E.g., if the intention of the action is to move an object to a location in the scene, the name of the action will be MoveObjectToLocation.action.