ROS2 Concepts in Practice #4 – Interfaces

ROS2 Concepts in Practice #4 – Interfaces

What we are going to learn

In this video, you’ll understand what is a ROS2 interface, the common language behind ROS2 messages, services, and actions.

You will also learn:

  • How to create and compile your own ROS2 Message Interfaces
  • How to create and compile your own ROS2 Service Interfaces

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4a5c5215/
  2. ROS Development Studio (ROSDS) —▸ http://rosds.online
  3. ROS2 Courses –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

Opening the rosject

In order to better understand ROS2 Interfaces, we need to have ROS2 installed in our system, and sometimes it is also useful to have some simulations. To make your life easier, we already prepared a rosject that you can use, with ROS2 already installed: https://app.theconstructsim.com/#/l/4a5c5215/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject (below you have a rosject example).

Learn ROS2 Parameters - Run rosject

Learn ROS2 – Run rosject (example of the RUN button)

 

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

ROS2 Interfaces overview

ROS2 Interfaces is a common language behind ROS2 messages, services, and actions.

Let’s start listing the interfaces. For that, let’s first open a terminal:

Open a new Terminal

Open a new Terminal

After the terminal is open, you can list the interfaces with the command ros2 interface list. The output would be something similar to the following:

ros2 interface list

Messages:
    action_msgs/msg/GoalInfo
    action_msgs/msg/GoalStatus
    action_msgs/msg/GoalStatusArray
    actionlib_msgs/msg/GoalID
    actionlib_msgs/msg/GoalStatus
    actionlib_msgs/msg/GoalStatusArray
    bond/msg/Constants
    bond/msg/Status
    builtin_interfaces/msg/Duration
    builtin_interfaces/msg/Time
    cartographer_ros_msgs/msg/LandmarkEntry
    cartographer_ros_msgs/msg/LandmarkList
    cartographer_ros_msgs/msg/SensorTopics
    cartographer_ros_msgs/msg/StatusCode
    cartographer_ros_msgs/msg/StatusResponse
    cartographer_ros_msgs/msg/SubmapEntry
    cartographer_ros_msgs/msg/SubmapList
    cartographer_ros_msgs/msg/SubmapTexture
    cartographer_ros_msgs/msg/TrajectoryOptions
    checking_interfaces/msg/NewMsg
    control_msgs/msg/DynamicJointState
    control_msgs/msg/GripperCommand
    control_msgs/msg/InterfaceValue
    control_msgs/msg/JointComponentTolerance
    control_msgs/msg/JointControllerState
    control_msgs/msg/JointJog
    control_msgs/msg/JointTolerance
    control_msgs/msg/JointTrajectoryControllerState
    control_msgs/msg/PidState
    controller_manager_msgs/msg/ControllerState
    controller_manager_msgs/msg/HardwareInterface
    diagnostic_msgs/msg/DiagnosticArray
    diagnostic_msgs/msg/DiagnosticStatus
    diagnostic_msgs/msg/KeyValue
    dwb_msgs/msg/CriticScore
    dwb_msgs/msg/LocalPlanEvaluation
    dwb_msgs/msg/Trajectory2D
    dwb_msgs/msg/TrajectoryScore
    example_interfaces/msg/Bool
    example_interfaces/msg/Byte
    example_interfaces/msg/ByteMultiArray
    example_interfaces/msg/Char
    example_interfaces/msg/Empty
    example_interfaces/msg/Float32
    example_interfaces/msg/Float32MultiArray
    example_interfaces/msg/Float64
    example_interfaces/msg/Float64MultiArray
    example_interfaces/msg/Int16
    example_interfaces/msg/Int16MultiArray
    example_interfaces/msg/Int32
    example_interfaces/msg/Int32MultiArray
    example_interfaces/msg/Int64
    example_interfaces/msg/Int64MultiArray
    example_interfaces/msg/Int8
    example_interfaces/msg/Int8MultiArray
    example_interfaces/msg/MultiArrayDimension
    example_interfaces/msg/MultiArrayLayout
    example_interfaces/msg/String
    example_interfaces/msg/UInt16
    example_interfaces/msg/UInt16MultiArray
    example_interfaces/msg/UInt32
    example_interfaces/msg/UInt32MultiArray
    example_interfaces/msg/UInt64
    example_interfaces/msg/UInt64MultiArray
    example_interfaces/msg/UInt8
    example_interfaces/msg/UInt8MultiArray
    example_interfaces/msg/WString
    gazebo_msgs/msg/ContactState
    gazebo_msgs/msg/ContactsState
    gazebo_msgs/msg/EntityState
    gazebo_msgs/msg/LinkState
    gazebo_msgs/msg/LinkStates
    gazebo_msgs/msg/ModelState
    gazebo_msgs/msg/ModelStates
    gazebo_msgs/msg/ODEJointProperties
    gazebo_msgs/msg/ODEPhysics
    gazebo_msgs/msg/PerformanceMetrics
    gazebo_msgs/msg/SensorPerformanceMetric
    gazebo_msgs/msg/WorldState
    geometry_msgs/msg/Accel
    geometry_msgs/msg/AccelStamped
    geometry_msgs/msg/AccelWithCovariance
    geometry_msgs/msg/AccelWithCovarianceStamped
    geometry_msgs/msg/Inertia
    geometry_msgs/msg/InertiaStamped
    geometry_msgs/msg/Point
    geometry_msgs/msg/Point32
    geometry_msgs/msg/PointStamped
    geometry_msgs/msg/Polygon
    geometry_msgs/msg/PolygonStamped
    geometry_msgs/msg/Pose
    geometry_msgs/msg/Pose2D
    geometry_msgs/msg/PoseArray
    geometry_msgs/msg/PoseStamped
    geometry_msgs/msg/PoseWithCovariance
    geometry_msgs/msg/PoseWithCovarianceStamped
    geometry_msgs/msg/Quaternion
    geometry_msgs/msg/QuaternionStamped
    geometry_msgs/msg/Transform
    geometry_msgs/msg/TransformStamped
    geometry_msgs/msg/Twist
    geometry_msgs/msg/TwistStamped
    geometry_msgs/msg/TwistWithCovariance
    geometry_msgs/msg/TwistWithCovarianceStamped
    geometry_msgs/msg/Vector3
    geometry_msgs/msg/Vector3Stamped
    geometry_msgs/msg/Wrench
    geometry_msgs/msg/WrenchStamped
    libstatistics_collector/msg/DummyMessage
    lifecycle_msgs/msg/State
    lifecycle_msgs/msg/Transition
    lifecycle_msgs/msg/TransitionDescription
    lifecycle_msgs/msg/TransitionEvent
    map_msgs/msg/OccupancyGridUpdate
    map_msgs/msg/PointCloud2Update
    map_msgs/msg/ProjectedMap
    map_msgs/msg/ProjectedMapInfo
    nav2_msgs/msg/BehaviorTreeLog
    nav2_msgs/msg/BehaviorTreeStatusChange
    nav2_msgs/msg/Costmap
    nav2_msgs/msg/CostmapFilterInfo
    nav2_msgs/msg/CostmapMetaData
    nav2_msgs/msg/Particle
    nav2_msgs/msg/ParticleCloud
    nav2_msgs/msg/SpeedLimit
    nav2_msgs/msg/VoxelGrid
    nav_2d_msgs/msg/Path2D
    nav_2d_msgs/msg/Pose2D32
    nav_2d_msgs/msg/Pose2DStamped
    nav_2d_msgs/msg/Twist2D
    nav_2d_msgs/msg/Twist2D32
    nav_2d_msgs/msg/Twist2DStamped
    nav_msgs/msg/GridCells
    nav_msgs/msg/MapMetaData
    nav_msgs/msg/OccupancyGrid
    nav_msgs/msg/Odometry
    nav_msgs/msg/Path
    pcl_msgs/msg/ModelCoefficients
    pcl_msgs/msg/PointIndices
    pcl_msgs/msg/PolygonMesh
    pcl_msgs/msg/Vertices
    pendulum_msgs/msg/JointCommand
    pendulum_msgs/msg/JointState
    pendulum_msgs/msg/RttestResults
    rcl_interfaces/msg/FloatingPointRange
    rcl_interfaces/msg/IntegerRange
    rcl_interfaces/msg/ListParametersResult
    rcl_interfaces/msg/Log
    rcl_interfaces/msg/Parameter
    rcl_interfaces/msg/ParameterDescriptor
    rcl_interfaces/msg/ParameterEvent
    rcl_interfaces/msg/ParameterEventDescriptors
    rcl_interfaces/msg/ParameterType
    rcl_interfaces/msg/ParameterValue
    rcl_interfaces/msg/SetParametersResult
    rmw_dds_common/msg/Gid
    rmw_dds_common/msg/NodeEntitiesInfo
    rmw_dds_common/msg/ParticipantEntitiesInfo
    rosgraph_msgs/msg/Clock
    sensor_msgs/msg/BatteryState
    sensor_msgs/msg/CameraInfo
    sensor_msgs/msg/ChannelFloat32
    sensor_msgs/msg/CompressedImage
    sensor_msgs/msg/FluidPressure
    sensor_msgs/msg/Illuminance
    sensor_msgs/msg/Image
    sensor_msgs/msg/Imu
    sensor_msgs/msg/JointState
    sensor_msgs/msg/Joy
    sensor_msgs/msg/JoyFeedback
    sensor_msgs/msg/JoyFeedbackArray
    sensor_msgs/msg/LaserEcho
    sensor_msgs/msg/LaserScan
    sensor_msgs/msg/MagneticField
    sensor_msgs/msg/MultiDOFJointState
    sensor_msgs/msg/MultiEchoLaserScan
    sensor_msgs/msg/NavSatFix
    sensor_msgs/msg/NavSatStatus
    sensor_msgs/msg/PointCloud
    sensor_msgs/msg/PointCloud2
    sensor_msgs/msg/PointField
    sensor_msgs/msg/Range
    sensor_msgs/msg/RegionOfInterest
    sensor_msgs/msg/RelativeHumidity
    sensor_msgs/msg/Temperature
    sensor_msgs/msg/TimeReference
    shape_msgs/msg/Mesh
    shape_msgs/msg/MeshTriangle
    shape_msgs/msg/Plane
    shape_msgs/msg/SolidPrimitive
    statistics_msgs/msg/MetricsMessage
    statistics_msgs/msg/StatisticDataPoint
    statistics_msgs/msg/StatisticDataType
    std_msgs/msg/Bool
    std_msgs/msg/Byte
    std_msgs/msg/ByteMultiArray
    std_msgs/msg/Char
    std_msgs/msg/ColorRGBA
    std_msgs/msg/Empty
    std_msgs/msg/Float32
    std_msgs/msg/Float32MultiArray
    std_msgs/msg/Float64
    std_msgs/msg/Float64MultiArray
    std_msgs/msg/Header
    std_msgs/msg/Int16
    std_msgs/msg/Int16MultiArray
    std_msgs/msg/Int32
    std_msgs/msg/Int32MultiArray
    std_msgs/msg/Int64
    std_msgs/msg/Int64MultiArray
    std_msgs/msg/Int8
    std_msgs/msg/Int8MultiArray
    std_msgs/msg/MultiArrayDimension
    std_msgs/msg/MultiArrayLayout
    std_msgs/msg/String
    std_msgs/msg/UInt16
    std_msgs/msg/UInt16MultiArray
    std_msgs/msg/UInt32
    std_msgs/msg/UInt32MultiArray
    std_msgs/msg/UInt64
    std_msgs/msg/UInt64MultiArray
    std_msgs/msg/UInt8
    std_msgs/msg/UInt8MultiArray
    stereo_msgs/msg/DisparityImage
    test_msgs/msg/Arrays
    test_msgs/msg/BasicTypes
    test_msgs/msg/BoundedSequences
    test_msgs/msg/Builtins
    test_msgs/msg/Constants
    test_msgs/msg/Defaults
    test_msgs/msg/Empty
    test_msgs/msg/MultiNested
    test_msgs/msg/Nested
    test_msgs/msg/Strings
    test_msgs/msg/UnboundedSequences
    test_msgs/msg/WStrings
    tf2_msgs/msg/TF2Error
    tf2_msgs/msg/TFMessage
    trajectory_msgs/msg/JointTrajectory
    trajectory_msgs/msg/JointTrajectoryPoint
    trajectory_msgs/msg/MultiDOFJointTrajectory
    trajectory_msgs/msg/MultiDOFJointTrajectoryPoint
    turtlesim/msg/Color
    turtlesim/msg/Pose
    unique_identifier_msgs/msg/UUID
    visualization_msgs/msg/ImageMarker
    visualization_msgs/msg/InteractiveMarker
    visualization_msgs/msg/InteractiveMarkerControl
    visualization_msgs/msg/InteractiveMarkerFeedback
    visualization_msgs/msg/InteractiveMarkerInit
    visualization_msgs/msg/InteractiveMarkerPose
    visualization_msgs/msg/InteractiveMarkerUpdate
    visualization_msgs/msg/Marker
    visualization_msgs/msg/MarkerArray
    visualization_msgs/msg/MenuEntry
Services:
    action_msgs/srv/CancelGoal
    cartographer_ros_msgs/srv/FinishTrajectory
    cartographer_ros_msgs/srv/StartTrajectory
    cartographer_ros_msgs/srv/SubmapQuery
    cartographer_ros_msgs/srv/WriteState
    checking_interfaces/srv/NewServiceMessage
    composition_interfaces/srv/ListNodes
    composition_interfaces/srv/LoadNode
    composition_interfaces/srv/UnloadNode
    control_msgs/srv/QueryCalibrationState
    control_msgs/srv/QueryTrajectoryState
    controller_manager_msgs/srv/ConfigureController
    controller_manager_msgs/srv/ConfigureStartController
    controller_manager_msgs/srv/ListControllerTypes
    controller_manager_msgs/srv/ListControllers
    controller_manager_msgs/srv/ListHardwareInterfaces
    controller_manager_msgs/srv/LoadConfigureController
    controller_manager_msgs/srv/LoadController
    controller_manager_msgs/srv/LoadStartController
    controller_manager_msgs/srv/ReloadControllerLibraries
    controller_manager_msgs/srv/SwitchController
    controller_manager_msgs/srv/UnloadController
    diagnostic_msgs/srv/AddDiagnostics
    diagnostic_msgs/srv/SelfTest
    dwb_msgs/srv/DebugLocalPlan
    dwb_msgs/srv/GenerateTrajectory
    dwb_msgs/srv/GenerateTwists
    dwb_msgs/srv/GetCriticScore
    dwb_msgs/srv/ScoreTrajectory
    example_interfaces/srv/AddTwoInts
    example_interfaces/srv/SetBool
    example_interfaces/srv/Trigger
    gazebo_msgs/srv/ApplyBodyWrench
    gazebo_msgs/srv/ApplyJointEffort
    gazebo_msgs/srv/ApplyLinkWrench
    gazebo_msgs/srv/BodyRequest
    gazebo_msgs/srv/DeleteEntity
    gazebo_msgs/srv/DeleteLight
    gazebo_msgs/srv/DeleteModel
    gazebo_msgs/srv/GetEntityState
    gazebo_msgs/srv/GetJointProperties
    gazebo_msgs/srv/GetLightProperties
    gazebo_msgs/srv/GetLinkProperties
    gazebo_msgs/srv/GetLinkState
    gazebo_msgs/srv/GetModelList
    gazebo_msgs/srv/GetModelProperties
    gazebo_msgs/srv/GetModelState
    gazebo_msgs/srv/GetPhysicsProperties
    gazebo_msgs/srv/GetWorldProperties
    gazebo_msgs/srv/JointRequest
    gazebo_msgs/srv/LinkRequest
    gazebo_msgs/srv/SetEntityState
    gazebo_msgs/srv/SetJointProperties
    gazebo_msgs/srv/SetJointTrajectory
    gazebo_msgs/srv/SetLightProperties
    gazebo_msgs/srv/SetLinkProperties
    gazebo_msgs/srv/SetLinkState
    gazebo_msgs/srv/SetModelConfiguration
    gazebo_msgs/srv/SetModelState
    gazebo_msgs/srv/SetPhysicsProperties
    gazebo_msgs/srv/SpawnEntity
    gazebo_msgs/srv/SpawnModel
    lifecycle_msgs/srv/ChangeState
    lifecycle_msgs/srv/GetAvailableStates
    lifecycle_msgs/srv/GetAvailableTransitions
    lifecycle_msgs/srv/GetState
    logging_demo/srv/ConfigLogger
    map_msgs/srv/GetMapROI
    map_msgs/srv/GetPointMap
    map_msgs/srv/GetPointMapROI
    map_msgs/srv/ProjectedMapsInfo
    map_msgs/srv/SaveMap
    map_msgs/srv/SetMapProjections
    nav2_msgs/srv/ClearCostmapAroundRobot
    nav2_msgs/srv/ClearCostmapExceptRegion
    nav2_msgs/srv/ClearEntireCostmap
    nav2_msgs/srv/GetCostmap
    nav2_msgs/srv/LoadMap
    nav2_msgs/srv/ManageLifecycleNodes
    nav2_msgs/srv/SaveMap
    nav_msgs/srv/GetMap
    nav_msgs/srv/GetPlan
    nav_msgs/srv/LoadMap
    nav_msgs/srv/SetMap
    pcl_msgs/srv/UpdateFilename
    rcl_interfaces/srv/DescribeParameters
    rcl_interfaces/srv/GetParameterTypes
    rcl_interfaces/srv/GetParameters
    rcl_interfaces/srv/ListParameters
    rcl_interfaces/srv/SetParameters
    rcl_interfaces/srv/SetParametersAtomically
    rosbag2_interfaces/srv/GetRate
    rosbag2_interfaces/srv/IsPaused
    rosbag2_interfaces/srv/Pause
    rosbag2_interfaces/srv/PlayNext
    rosbag2_interfaces/srv/Resume
    rosbag2_interfaces/srv/Seek
    rosbag2_interfaces/srv/SetRate
    rosbag2_interfaces/srv/TogglePaused
    sensor_msgs/srv/SetCameraInfo
    slam_toolbox/srv/AddSubmap
    slam_toolbox/srv/Clear
    slam_toolbox/srv/ClearQueue
    slam_toolbox/srv/DeserializePoseGraph
    slam_toolbox/srv/LoopClosure
    slam_toolbox/srv/MergeMaps
    slam_toolbox/srv/Pause
    slam_toolbox/srv/SaveMap
    slam_toolbox/srv/SerializePoseGraph
    slam_toolbox/srv/ToggleInteractive
    std_srvs/srv/Empty
    std_srvs/srv/SetBool
    std_srvs/srv/Trigger
    test_bond/srv/TestBond
    test_msgs/srv/Arrays
    test_msgs/srv/BasicTypes
    test_msgs/srv/Empty
    tf2_msgs/srv/FrameGraph
    turtlesim/srv/Kill
    turtlesim/srv/SetPen
    turtlesim/srv/Spawn
    turtlesim/srv/TeleportAbsolute
    turtlesim/srv/TeleportRelative
    visualization_msgs/srv/GetInteractiveMarkers
Actions:
    action_tutorials_interfaces/action/Fibonacci
    control_msgs/action/FollowJointTrajectory
    control_msgs/action/GripperCommand
    control_msgs/action/JointTrajectory
    control_msgs/action/PointHead
    control_msgs/action/SingleJointPosition
    example_interfaces/action/Fibonacci
    nav2_msgs/action/BackUp
    nav2_msgs/action/ComputePathThroughPoses
    nav2_msgs/action/ComputePathToPose
    nav2_msgs/action/DummyRecovery
    nav2_msgs/action/FollowPath
    nav2_msgs/action/FollowWaypoints
    nav2_msgs/action/NavigateThroughPoses
    nav2_msgs/action/NavigateToPose
    nav2_msgs/action/Spin
    nav2_msgs/action/Wait
    test_msgs/action/Fibonacci
    test_msgs/action/NestedMessage
    tf2_msgs/action/LookupTransform
    turtlesim/action/RotateAbsolute

 

As you can see in the output above, the same command returned Messages, Actions, and Services.

Creating our first ROS2 Interface (a message)

If you are using the rosject provided at the beginning of this post, there is already a ROS2 Package called checking_interfaces on the /home/user/ros2_ws/src/checking_interfaces path. The package also already contains a message on the ~/ros2_ws/src/checking_interfaces/msg/NewMsg.msg path.

If you are not using the provided rosject, you can create a package with:

mkdir -p /home/user/ros2_ws/src

cd /home/user/ros2_ws/src

ros2 pkg create --build-type ament_cmake checking_interfaces

The output would be similar to the following:

going to create a new package
package name: checking_interfaces
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./checking_interfaces
creating ./checking_interfaces/package.xml
creating source and include folder
creating folder ./checking_interfaces/src
creating folder ./checking_interfaces/include/checking_interfaces
creating ./checking_interfaces/CMakeLists.txt

 

If you are using the rosject you do not need to create the message msg/NewMsg.msg, but for learning purposes, let’s see how to create it.

~/ros2_ws/src/checking_interfaces$ ls ~/ros2_ws/src/checking_interfaces$ ls msg/NewMsg.msg

cd ~/ros2_ws/src/checking_interfaces

mkdir msg

cd msg

touch NewMsg.msg

Then, paste the following content on the NewMsg.msg file:

bool check
int16 number
string text

If you are wondering how to know which types are accepted for the properties of the new message, the types available are:

ROS2 build-in-types

Type name C++ Python DDS type
bool bool builtins.bool boolean
byte uint8_t builtins.bytes* octet
char char builtins.str* char
float32 float builtins.float* float
float64 double builtins.float* double
int8 int8_t builtins.int* octet
uint8 uint8_t builtins.int* octet
int16 int16_t builtins.int* short
uint16 uint16_t builtins.int* unsigned short
int32 int32_t builtins.int* long
uint32 uint32_t builtins.int* unsigned long
int64 int64_t builtins.int* long long
uint64 uint64_t builtins.int* unsigned long long
string std::string builtins.str string
wstring std::u16string builtins.str string

Compiling our ROS2 Interface

In order to compile our ROS2 Interface, we have to touch the ~/ros2_ws/src/checking_interfaces/CMakeLists.txt file. Again, if you are using the rosject we provided, everything is already prepared for you. For learning purposes, let’s open the file to modify it. Let’s start by opening the Code Editor:

Open the IDE - Code Editor

Open the IDE – Code Editor

 

After the Code Editor is open, feel free to open the checking_interfaces/CMakeListst.txt file.

Around line 13 of the CMakeListst.txt file, we have to add the following lines:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
	"msg/NewMsg.msg"
)

The first line mentioned above includes the rosidl_default_generators package, which will be used to “compile” our message to make it available for Python and C++, so that our nodes can use the interface.

In the rosidl_generate_interfaces we add the name of the interface (message, service, or action) that we want to compile, which in this case is “msg/NewMsg.msg”. 

After setting up the CMakeLists.txt file, you also have to set up the checking_interfaces/package.xml file. We have to add rosidl_default_generators as a build dependency, we also need to add the rosidl_default_runtime execution dependency, and we also need the rosidl_interface_packages “member of group”. In the end, the lines you would need to add are:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

In the end, the final package.xml file would be as follows:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
  <package format="3">
  <name>checking_interfaces</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintaineremail="user@todo.todo">user</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
After saving the files, you can compile the interface with:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 

The package should compile with no errors:

...
This may be promoted to an error in a future release of colcon-core.
Starting >>> checking_interfaces
Finished <<< checking_interfaces [5.27s]

Summary: 1 package finished [5.87s]

 

After the package is compiled, we can now source the installation folder so that ROS can find what we have compiled so far:

source install/setup.bash

 

If we now list the interfaces again and search for our NewMsg, we should be able to find it:

ros2 interface list | grep New

# ...
checking_interfaces/msg/NewMsg
# ...

As we can see in the output above, we have a message called NewMsg.

Using the interface we just created

If we have just created the checking_interfaces/msg/NewMsg, we should be able to use it. We can check its definition with:

ros2 interface show checking_interfaces/msg/NewMsg

which shows the exact thing we defined in the NewMsg.msg file:

bool check
int16 number
string text

 

Let’s now create a publisher in a topic called /testing using this message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg

After pressing ENTER, we should see the message being published with default values:

 

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #2: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #3: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #4: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #5: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #6: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
publishing #7: checking_interfaces.msg.NewMsg(check=False, number=0, text='')
...

You can of course set the values of the message:

ros2 topic pub /testing checking_interfaces/msg/NewMsg "{check: true, number: 7, text: 'Be perfect' }"

# WHICH OUTPUTS

publisher: beginning loop
publishing #1: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #2: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
publishing #3: checking_interfaces.msg.NewMsg(check=True, number=7, text='Be perfect')
...

 

Creating our second ROS2 Interface (a service)

Similar to when defining our message, we already have a service defined on ~/ros2_ws/src/checking_interfaces/srv/NewServiceMessage.srv, but we will be following the process for learning purposes in case you are not using the rosject we provided, or wants to learn with hands-on.

cd ~/ros2_ws/src/checking_interfaces

mkdir srv

cd srv

touch NewServiceMessage.srv

You can now open the NewServiceMessage.srv file with the Code Editor and paste the following content on it:

bool check
---
int16 number
string text

In order to compile the service interface, you have to open the CMakeLists.txt file again and add “srv/NewServiceMessage.srv” right after “msg/NewMsg.msg” that we added earlier around line 15 of our CMakeLists.txt file. The rosidl_generate_interfaces section of the file would be like:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
    "msg/NewMsg.msg"
    "srv/NewServiceMessage.srv"
)
As you can see so far, the same rosidl_generate_interfaces is used to compile Messages and Services.
You may have noticed that we have a line containing “—“ in the NewServiceMessage.srv file. These three “—” basically separate the Service Request from the Service Response.
The request will be “bool check“, and the fields used in a response will be “int16 number” and “string text“.
After saving the file changes, we can just compile the service interface in the same way that we compiled the message interface:
cd ~/ros2_ws/

colcon build --packages-select checking_interfaces

 source install/setup.bash

If you now list the interfaces, you should be able to easily find our NewServiceMessage.

ros2 interface list | grep New


checking_interfaces/msg/NewMsg
checking_interfaces/srv/NewServiceMessage

 

Congratulations. You now know how to easily create ROS2 Interfaces. Please go ahead and try to create ROS2 Action Interfaces yourself, or you can also check the courses that we have listed at the end of this post.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

Get an RGB Camera Working in ROS2 and RVIZ2

Get an RGB Camera Working in ROS2 and RVIZ2

In this post, we will see how to get an RGB camera working in ROS2 and RVIZ2. You might already be used to doing this in ROS1 and RVIZ1, and it’s easy-peasy. In ROS2 however, it’s a bit tricky and you are about to learn how to break the codes.

Step 1: Grab a copy of the ROS Project containing the code

Click here to get your own copy of the project (PS: If you don’t have an account on the ROS Development Studio, you would need to create one. Once you create an account or log in, we will copy the project to your workspace).

That done, open the project using the Run button. This might take a few moments, please be patient.

You should now see a notebook with detailed instructions. This post includes a summary of these instructions and some other tips.

Step 2: Explore the source code using the IDE

Open Code Editor

Open the IDE by clicking on the icon as shown above. You should now see something similar to the image below:

All the files used in the simulation are in the ros2_ws/src directory. Explore the files. Double-click to open a file in the editor. You will refer back to some of the files later on.

Step 3: Study the main files needed to get an RGB camera working in ROS2 and RVIZ2

There are two main things you need to do to get this working:

  1. Add the camera to the URDF file (and launch the URDF file) so we can have the camera in Gazebo
  2. Create a robot state publisher node for the robot so we can visualize it in Rviz2

Let’s see these steps in details.

All the code for the simulation is in ros2_ws/src/box_bot/. Let’s examine the main files related to launching the camera, so you can understand how to implement yours.

ros2_ws/src/box_bot/box_bot_gazebo/launch/box_bot_launch.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os  from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description():

    pkg_box_bot_gazebo = get_package_share_directory('box_bot_gazebo')
    pkg_box_bot_description = get_package_share_directory('box_bot_description')

    # Start World
    start_world = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_box_bot_gazebo, 'launch', 'start_world_launch.py'),
        )
    )
    # Spawn the robot
    spawn_robot_world = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_box_bot_description, 'launch', 'spawn_robot_launch_v3.launch.py'),
        )
    )     

    return LaunchDescription([
        start_world,
        spawn_robot_world
    ])

The launch file above does two things:

  1. Spawns the world, by calling the launch file that spawns the world.
  2. Spawns the robot, by calling the launch file that spawn the robot. This is where our interest lies. Let’s see how this is done, in the launch file ros2_ws/src/box_bot/box_bot_description/launch/spawn_robot_launch_v3.launch.py.

ros2_ws/src/box_bot/box_bot_description/launch/spawn_robot_launch_v3.launch.py

import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node

def generate_launch_description():

    use_sim_time = LaunchConfiguration('use_sim_time', default='true')

    # Get the path to the URDF file
    urdf = os.path.join(get_package_share_directory('box_bot_description'), 'robot/', 'box_bot.urdf')
    assert os.path.exists(urdf), "Thebox_bot.urdf doesnt exist in "+str(urdf)
    
    # Open the URDF file
    with open(urdf, 'r') as infp:
        robot_desc = infp.read()

    return LaunchDescription([
        DeclareLaunchArgument(
            'use_sim_time',
            default_value='false',
            description='Use simulation (Gazebo) clock if true'),
        Node(package='box_bot_description', executable='spawn_box_bot.py', arguments=[urdf], output='screen'),
        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            name='robot_state_publisher',
            output='screen',
            parameters=[{'use_sim_time': use_sim_time, 'robot_description': robot_desc}],
            arguments=[urdf]),
    ])

The launch file above does two things:

  1. Creates a node that that spawns the box bot with the camera, taking the path to the URDF file as the argument. The node is implemented in the file spawn_bot_bot.py file. We’ll look at this file next.
  2. Creates a node that publishes the robot state. It takes the URDF file string as a parameter. This will be used by Rviz2.

ros2_ws/src/box_bot/box_bot_description/launch/spawn_box_bot.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import sys
import rclpy
from gazebo_msgs.srv import SpawnEntity

def main(args=None):
    rclpy.init(args=args)
    node = rclpy.create_node('minimal_client')
    cli = node.create_client(SpawnEntity, '/spawn_entity')

    content = ""
    if sys.argv[1] is not None:
        with open(sys.argv[1], 'r') as content_file:
            content = content_file.read()

    req = SpawnEntity.Request()
    req.name = "box_bot"
    req.xml = content
    req.robot_namespace = ""
    req.reference_frame = "world"

    while not cli.wait_for_service(timeout_sec=1.0):
        node.get_logger().info('service not available, waiting again...')

    future = cli.call_async(req)
    rclpy.spin_until_future_complete(node, future)

    if future.result() is not None:
        node.get_logger().info(
            'Result ' + str(future.result().success) + " " + future.result().status_message)
    else:
        node.get_logger().info('Service call failed %r' % (future.exception(),))

    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

The file simply takes the URDF file passed to it and spawns the robot.

Now, let’s see the URDF file itself. The part that adds the camera is labelled Camera, somewhere in the middle of the file. Locate the file in the IDE:

ros2_ws/src/box_bot/box_bot_description/robot/box_bot.urdf

Step 4: Launch the Simulation and Rviz2 to see the RGB camera

Open webshell

Open a web shell and run the following command:

user:~$ cd ~/ros2_wsuser:~/ros2_ws$ source install/setup.bash
user:~/ros2_ws$ ros2 launch box_bot_gazebo box_bot_launch.py

Open Gazebo

Open the Gazebo app (if it does not open automatically). You should see a simulation similar to this one:

Box Bot with Camera

Next, let’s launch Rviz2 to see the camera. In another web shell, type:

rviz2

Now open the graphical tools app (if it does not open automatically).

Open graphical tools

You should now see the Rviv2 window. Set the Fixed Frame to base_link and click Add to add an Image display.

Add Display

Select image display

Expand the Image display, then expand the Topic property. Select the /rgb_cam/image_raw topic and set the Reliability to Best Effort. There you go!

Visualize Image

Step 5: Consolidate your learning

Do you understand how to get an RGB camera working in ROS2 and RVIZ2? Are you able to implement a camera in your own simulation? If not, please go over the post again and maybe watch the video below? Let us know what worked for you in the comments.

Extra Step: Watch the video to see the sights and sounds version how to get an RGB camera working in ROS2 and RVIZ2

Here you go:

Related Resources

Feedback

Did you like this post? Do you have any questions about the explanations? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS2 topics, please let us know in the comments area and we will do a video or post about it.


				
					
Learn TF2 – ROS2 Concepts in Practice

Learn TF2 – ROS2 Concepts in Practice

What we are going to learn

  1. How to create transformation frames
  2. How to get the position of one frame in relation to another that is not directly related
  3. How to create and publish as many frames as needed inside a ROS2 node

List of resources used in this post

  1. The Construct: https://app.theconstructsim.com/
  2. Use this rosject: https://app.theconstructsim.com/#/l/4a13e8b0/
  3. ROS2 Tutorials –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61
    3. ROS2 Navigation (https://bit.ly/3swohlO)
  4. TF2 documentation: https://docs.ros.org/en/foxy/Concepts/About-Tf2.html

TF2 Overview

As we can see on https://docs.ros.org/en/foxy/Concepts/About-Tf2.html, tf2 is the transform library for ROS2. It maintains the relationship between coordinate frames in a tree structure buffered in time and lets the user transform points between any two coordinate frames at any desired point in time.

Creating a rosject

In order to learn TF2 hands-on, we need to have a system with ROS installed. We are going to use The Construct (https://app.theconstructsim.com/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

If you do not want to create a rosject and do everything manually, you can use the rosject we already created (https://app.theconstructsim.com/#/l/4a13e8b0/). Just click the link.

Once inside, if you did not decide to use the existing rosject, let’s click My Rosjects on the left side and then, Create a new rosject.

Create a new rosject

Create a new rosject

Let’s select ROS2 Foxy for the ROS Distro of the rosject, let’s name the rosject as you want. You can leave the rosject public. You should see the rosject you just created in your rosjects list (the name is certainly different from the example below that was added just for learning purposes)

List of rosjects - Using Depth camera in ROS2 to determine object distance

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Creating a ROS2 Python package

After pressing the Run button, you should now have the rosject open. Now, it’s time to create a ROS2 Python package if you had to create your own rosject. If you used the rosject we shared at the beginning of this post, you don’t need to create the package. To create the package, let’s start by opening a terminal:

Open a new Terminal

Open a new Terminal

Now, in this first terminal, let’s run the following command to create our package named tf2_examples:

cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python --node-name my_node tf2_examples

If everything went fine, the output should be as follows:

going to create a new package
package name: tf2_examples
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user <user@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
node_name: my_node
creating folder ./tf2_examples
creating ./tf2_examples/package.xml
creating source folder
creating folder ./tf2_examples/tf2_examples
creating ./tf2_examples/setup.py
creating ./tf2_examples/setup.cfg
creating folder ./tf2_examples/resource
creating ./tf2_examples/resource/tf2_examples
creating ./tf2_examples/tf2_examples/__init__.py
creating folder ./tf2_examples/test
creating ./tf2_examples/test/test_copyright.py
creating ./tf2_examples/test/test_flake8.py
creating ./tf2_examples/test/test_pep257.py
creating ./tf2_examples/tf2_examples/my_node.py

As we can see in the logs, we already have a node called ./tf2_examples/tf2_examples/my_node.py and a ./tf2_examples/setup.py.

Preparing our python package to have launch files

In order to be able to have launch files in our package, we need to modify the ~/ros2_ws/src/tf2_examples/setup.py file.

We have basically to import glob and modify the data_files variable.  To make our life easier, I’m going to paste here the full content of the setup.py file after our modifications (bear in mind that if you used the rosject provided at the beginning, the file will already contain the correct code):

import os
from glob import glob
from setuptools import setup

package_name = 'tf2_examples'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'),
         glob(os.path.join('launch', '*.launch.py'))),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_node = tf2_examples.my_node:main'
        ],
    },
)

Installing dependencies

On The Construct, we do not always have all packages required for all purposes. For this TF2 demonstration, we need to have some packages. Lets install them by running the following commands in a second terminal:

sudo apt-get update

sudo apt-get install -y ros-foxy-turtle-tf2-py ros-foxy-tf2-tools ros-foxy-tf-transformations

pip3 install transforms3d

 

Creating a frame publisher

If you have used the rosject we provided at the beginning, you already have the my_node.py with a Frame Publisher set.

If you created a rosject manually, however, you need to manually replace the content of my_node.py with the following content taken from the official ROS2 Tutorials:

from geometry_msgs.msg import TransformStamped

import rclpy
from rclpy.node import Node

from tf2_ros import TransformBroadcaster

import tf_transformations

from turtlesim.msg import Pose


class FramePublisher(Node):

    def __init__(self):
        super().__init__('turtle_tf2_frame_publisher')

        # Declare and acquire `turtlename` parameter
        self.declare_parameter('turtlename', 'turtle')
        self.turtlename = self.get_parameter(
            'turtlename').get_parameter_value().string_value

        # Initialize the transform broadcaster
        self.br = TransformBroadcaster(self)

        # Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose
        # callback function on each message
        self.subscription = self.create_subscription(
            Pose,
            f'/{self.turtlename}/pose',
            self.handle_turtle_pose,
            1)

    def handle_turtle_pose(self, msg):
        t = TransformStamped()
        t2 = TransformStamped()

        # Read message content and assign it to
        # corresponding tf variables
        t.header.stamp = self.get_clock().now().to_msg()
        t.header.frame_id = 'world'
        t.child_frame_id = self.turtlename
        # turtle/drone
        t2.header.stamp = self.get_clock().now().to_msg()
        t2.header.frame_id = self.turtlename
        t2.child_frame_id = 'drone'

        # Turtle only exists in 2D, thus we get x and y translation
        # coordinates from the message and set the z coordinate to 0
        t.transform.translation.x = msg.x - (11.08888/2)
        t.transform.translation.y = msg.y - (11.08888/2)
        t.transform.translation.z = 0.0

        t2.transform.translation.x = -0.5
        t2.transform.translation.y = 0.0
        t2.transform.translation.z = 1.0

        # For the same reason, turtle can only rotate around one axis
        # and this why we set rotation in x and y to 0 and obtain
        # rotation in z axis from the message
        q = tf_transformations.quaternion_from_euler(0, 0, msg.theta)
        t.transform.rotation.x = q[0]
        t.transform.rotation.y = q[1]
        t.transform.rotation.z = q[2]
        t.transform.rotation.w = q[3]

        t2.transform.rotation.x = 0.0
        t2.transform.rotation.y = 0.0
        t2.transform.rotation.z = 0.0
        t2.transform.rotation.w = 1.0

        # Send the transformation
        self.br.sendTransform(t)
        self.br.sendTransform(t2)


def main():
    rclpy.init()
    node = FramePublisher()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass

    rclpy.shutdown()

 

If you check carefully the code of my_node.py, you can see that around line 24 we start a TF Broadcaster:

# Initialize the transform broadcaster
self.br = TransformBroadcaster(self)
we then create a subscriber to a /turtle{1}{2}/pose topic and call handle_turtle_pose method when messages arrive on this topic. I highly recommend you have a look at the comments and the code of the handle_turtle_pose to better understand it. It not only makes you exercise your brain but also makes you better understand things by yourself.

Creating a launch file

Now that we have our code in place, we need a launch file. If you remember well, the setup.py file is already prepared for launch files. We now need to create the launch files. Bear in mind that if you are using the rosject we provided at the beginning of the post, this launch folder and its python file already exist.

Assuming you created a rosject manually, let’s create a launch folder using the terminal:

cd ~/ros2_ws/src/tf2_examples

mkdir launch

cd launch

touch tf2_example.launch.py
If you just created the tf2_example.launch.py file, it may be empty. If that is the case, just copy the following content to it:
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='sim'
        ),
        Node(
            package='tf2_examples',
            executable='my_node',
            name='broadcaster1',
            parameters=[
                {'turtlename': 'turtle1'}
            ]
        ),
    ])
By looking at the launch file we just created, we see that we are basically launching the turtlesim simulation and our broadcaster node.

Compiling our ros2_ws workspace

Now that our launch files are ready, we can compile our ros2_ws workspace. Let’s do that with the following commands:

cd ~/ros2_ws

colcon build
If everything went ok, you should have no error messages and the logs should be similar to the following:
user:~/ros2_ws$ colcon build

Starting >>> my_package
Starting >>> tf2_examples
Finished <<< my_package [2.17s]
Finished <<< tf2_examples [2.14s]
Summary: 2 packages finished [2.65s]
If you are wondering why you need to compile the workspace if we are using only python, not C++, the answer is: we need to compile so that ROS2 can copy the launch files to the common share folder of the workspace.

Launching the turtle simulation using ros2 launch files

Now that our package is compiled and everything in place, let’s launch the launch file we just created:

cd ~/ros2_ws/

source install/setup.bash

ros2 launch tf2_examples tf2_example.launch.py
You should now have a simulation window that should have opened automatically with the turtlesim simulation on it.
If the simulation do not show automatically to you, you can just open the Graphical Tools:
Open Graphical Tools to see the turtlesim

Open Graphical Tools to see the turtlesim

If we now open a third terminal, we should be able to see our nodes there after typing ros2 node list:
ros2 node list

/broadcaster1
/sim

Open RViz2

Now that we have our simulation and our broadcaster running, we need RViz2 to see the frames.

Let’s open it running the following command in the third terminal:

ros2 run rviz2 rviz2

If rviz2 does not show automatically to you, you can just open the Graphical Tools as before.

Assuming RViz is running, remember to set the fixed frame (on the top left side) to world. You also have to click the Add button on the bottom left to add TF:

RViz add TF

RViz add TF

 

After having added the TF panel in RViz, you should see the frames as it happens in the center of the image above.

Moving the robot using the keyboard

Now that we have our simulation running, rviz running, and our TF panel added, we can move our robot to better understand the TFs.

TIP: In the Graphical Tools window, to see the Turtlesim and the TFs at the same time, you can put move the turtlesim panel to the left, and put RViz on the right side.

Ok, we can now open a fourth terminal and run the turtle_teleop_key node that allows us to move the robot using the keyboard:

ros2 run turtlesim turtle_teleop_key

You should now have the instructions to move the robot around:

Reading from keyboard
---------------------------
Use arrow keys to move the turtle.
Use G|B|V|C|D|E|R|T keys to rotate to absolute orientations. 'F' to cancel a rotation.
'Q' to quit.

 

By pressing the arrow keys, you can see that the robot moves, and at the same time we can see the frames in RViz.

If you look carefully, in RViz we see not only the frames for the turtle and the world, but we also have a drone frame.

We did that to simulate that we have a drone following the turtle at a fixed distance. I would recommend you have a look again at the handle_turtle_pose method defined in the my_node.py to better understand it.

In the code of handle_turtle_pose, we see that the drone is 0.5 meters behind the turtle and 1 meter above:

t2.transform.translation.x = -0.5
t2.transform.translation.y = 0.0
t2.transform.translation.z = 1.0

Whenever we receive a position from the turtle, we are publishing the position of the drone based on the position of the turtle.

Checking the position of the turtle related to the world using the command line

We saw that we can do a lot of tf2-related things using Python.

It is worth mentioning that we can also use the command line to check the position/distance of the turtle with relation to the world.

ROS2 has a package named tf2_ros with many executables and one of them is tf2_echo. We can know the position of the turtle with the following command:

ros2 run tf2_ros tf2_echo world turtle1

The output should be similar to the following:

At time 1649109418.661570822
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 1649109419.668374440
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]
At time 1649109420.660446225
- Translation: [-2.552, 0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.000, 1.000]

You can also check the position of the drone related to the position of the robot:

ros2 run tf2_ros tf2_echo turtle1 drone

You can see that the translation is exactly as set in the my_node.py file (half meters behind, 1 meter above):

- Translation: [-0.500, 0.000, 1.000]

If you are wondering whether or not you can know the position of the drone related to the world, you can know it also just change the parameters passed to the tf2_echo node:

ros2 run tf2_ros tf2_echo world drone

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

Separating ROS2 environments – ROS_DOMAIN_ID – ROS2 Concepts in Practice

Separating ROS2 environments – ROS_DOMAIN_ID – ROS2 Concepts in Practice

What we are going to learn

  1. How to configure ROS_DOMAIN_ID
  2. How to have separated ROS2 environments in the same network
  3. How to launch ROS2 programs in different environments

List of resources used in this post

  1. The Construct: https://app.theconstructsim.com/
  2. ROS2 Tutorials –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61
  3. ROS Domain ID documentation: https://docs.ros.org/en/foxy/Concepts/About-Domain-ID.html

Brief Start

Today we will be learning ROS_DOMAIN_ID, something new in ROS2 that works on top of DDS, the communication layer.

What it really does? It basically allows you to isolate two or more ROS environments in the same physical network.

Each DOMAIN_ID would allow you to have around 120 nodes.

Creating a rosject

In order to learn ROS_DOMAIN_ID with hands-on, we need to have a system with ROS installed. We are going to use The Construct (https://app.theconstructsim.com/) for this tutorial, but if you have ROS2 installed on your own computer, you should be able to do ~everything on your own computer, except this creating a rosject part.

Let’s start by opening The Construct (https://www.theconstruct.ai/) and logging in. You can easily create a free account if you still don’t have one.

Once inside, let’s click My Rosjects on the left side and then, Create a new rosject.

Create a new rosject

Create a new rosject

Let’s select ROS2 Foxy for the ROS Distro of the rosject, let’s name the rosject as you want. You can leave the rosject public. You should see the rosject you just created in your rosjects list (the name is certainly different from the example below that was added just for learning purposes)

List of rosjects - Using Depth camera in ROS2 to determine object distance

If you mouse over the recently created rosject, you should see a Run button. Just click that button to launch the rosject.

Running a publisher on ROS_DOMAIN_ID=0

Assuming that you have clicked the Run button and have your rosject running, let’s now create a publisher using the terminal. For that, let’s start by opening a terminal.

Open a new Terminal

Open a new Terminal

Now, let’s run the following command to run create a publisher in the first terminal:

ros2 topic pub -r 1 /string_topic std_msgs/String "{data: \"Hello from my domain\"}"

We should have the following output:

publisher: beginning loop
publishing #1: std_msgs.msg.String(data='Hello from my domain')
publishing #2: std_msgs.msg.String(data='Hello from my domain')
publishing #3: std_msgs.msg.String(data='Hello from my domain')
publishing #4: std_msgs.msg.String(data='Hello from my domain')
publishing #5: std_msgs.msg.String(data='Hello from my domain')
publishing #6: std_msgs.msg.String(data='Hello from my domain')
publishing #7: std_msgs.msg.String(data='Hello from my domain')
...

If you look carefully at the command that we typed, we are basically creating a topic called string_topic with std_msgs/String data type and we are publishing Hello from my domain every 1 second.

Running a ROS2 Subscriber on ROS_DOMAIN_ID=0

If we open a second terminal, we can see the topics by typing ros2 topic list and we should be able to see our string_topic:

user:~$ ros2 topic list
/parameter_events
/rosout
/string_topic

We can also subscribe to that topic and see what is being published there with ros2 topic echo /string_topic, which should output:

user:~$ ros2 topic echo /string_topic

data: Hello from my domain
---
data: Hello from my domain
---
data: Hello from my domain
---

 

Up to now we did not setup any ROS_DOMAIN_ID, but we are running on Domain 0 (zero). 

How do we know this? Because when we do not set any ROS_DOMAIN_ID, the default value is zero.

If we list the processes and grep for ros-domain, we can also see that the default ID is 0.

Let’s list the processes in a third terminal and search for ros-domain with the following command

ps -ax| grep ros-domain

We should see –ros-domain-id 0 at the end of the command line used to launch the ros2_daemon, which is launched automatically when you run your first ros2 command.

/usr/bin/python3 /opt/ros/foxy/bin/_ros2_daemon --rmw-implementation rmw_fastrtps_cpp --ros-domain-id 0

 

Publisher and Subscriber on ROS_DOMAIN_ID=1

Now, let’s go to the first terminal, stop the current publisher by pressing CTRL+C, and let’s relaunch the publisher but now setting the ROS_DOMAIN_ID=1.

ROS_DOMAIN_ID=1 ros2 topic pub -r 1 /string_topic std_msgs/String "{data: \"Hello from my 2ND domain\"}"

You should see that the publisher is running as expected:

publisher: beginning loop
publishing #1: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #2: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #3: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #4: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #5: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #6: std_msgs.msg.String(data='Hello from my 2ND domain')
publishing #7: std_msgs.msg.String(data='Hello from my 2ND domain')
...

 

But if we check the output of the subscriber in the second terminal, we see that the messages stopped coming.

We are not receiving the new messages in the second terminal, and the reason for that is simple: the subscriber is on ROS_DOMAIN_ID=0, and the publisher is on ROS_DOMAIN_ID=1. They are basically on different ROS Networks, that is why they do not see each other.

If we now stop the subscriber by pressing CTRL+C on the second terminal, we can run it again but on ROS_DOMAIN_ID=1 to see the messages being published:

ROS_DOMAIN_ID=1 ros2 topic echo /string_topic

We should see now that the messages are correctly coming:

data: Hello from my 2ND domain
---
data: Hello from my 2ND domain
---
data: Hello from my 2ND domain
...

ROS2 topic list on different ROS_DOMAIN_IDs

So far we learned that the ros2 topic pub and ros2 topic echo commands can use different domains, but it is worth mentioning that the ROS_DOMAIN_ID thing is not restricted only to these two commands. Any ROS2 command should respect this ROS_DOMAIN_ID.

If in the second terminal we stop the subscriber with CTRL+C and run ros2 topic list , we will not see the string_topic there:

ros2 topic list

/parameter_events
/rosout

But if we set the ROS_DOMAIN_ID to 1, we will see the string_topic topic there:

ROS_DOMAIN_ID=1 ros2 topic list

/parameter_events
/rosout
/string_topic

 

All right! We could go on with more examples, but I think you should now have understood how ROS_DOMAIN_ID works.

Here we did not explain why we can only have ~120 nodes in each ROS_DOMAIN_ID. We recommend you go to https://docs.ros.org/en/foxy/Concepts/About-Domain-ID.html and read it carefully to understand it better.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

How to use ROS2 parameters – ROS2 Q&A #229

How to use ROS2 parameters – ROS2 Q&A #229

What we are going to learn

  1. How to check for node parameters
  2. How to load parameters from terminal and launch files
  3. How to dump parameters into a file

List of resources used in this post

  1. Use the rosject: https://app.theconstructsim.com/#/l/4875b2e0/
  2. The Construct: https://app.theconstructsim.com/
  3. ROS2 Tutorials –▸
    1. ROS2 Basics in 5 Days (Python): https://app.theconstructsim.com/#/Course/73
    2. ROS2 Basics in 5 Days (C++): https://app.theconstructsim.com/#/Course/61

ROS2 Parameters key points

Before we start really using ROS2 parameters, let’s understand key points about them:

    • Parameters in ROS2 are implemented/attached to/served by each node individually, as opposed to the parameter server associated with roscore in ROS1, therefore, when the node dies, so do its parameters. This is easier to understand if you already know that in ROS2 we do not have roscore.
    • Parameters can be loaded at the node startup or while the node is running

Having this in mind, our life now understanding ROS2 params is going to become easier.

Opening the rosject

In order to learn how to load and retrieve ROS2 Parameters, we need to have ROS installed in our system, and it is also useful to have some simulations. We already prepared a rosject with a simulation for that: https://app.theconstructsim.com/#/l/4875b2e0/.

You can download the rosject on your own computer if you want to work locally, but just by copying the rosject (clicking the link), you will have a setup already prepared for you.

After the rosject has been successfully copied to your own area, you should see a Run button. Just click that button to launch the rosject.

Learn ROS2 Parameters - Run rosject

Learn ROS2 Parameters – Run rosject

 

After pressing the Run button, you should have the rosject loaded. Let’s now head to the next section to really get some real practice.

Launching the turtlebot simulation with ROS2

In order to launch the simulation, let’s start by opening a new terminal:

Open a new Terminal

Open a new Terminal

After having the first terminal open, let’s run the following commands to launch a simulation:

source ~/simulation_ws/install/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/simulation_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models
ros2 launch turtlebot3_gazebo empty_world.launch.py

 

Wait some seconds until the simulation is loaded. If for any reason the simulation does not load and you see error messages like the following:

[INFO] [1649093592.180346898] [spawn_entity]: Waiting for service /spawn_entity
[ERROR] [1649093597.566604708] [spawn_entity]: Service %s/spawn_entity unavailable. Was Gazebo started with GazeboRosFactory?
[ERROR] [1649093597.567565097] [spawn_entity]: Spawn service failed. Exiting.
[ERROR] [spawn_entity.py-4]: process has died [pid 1007, exit code 1, cmd '/opt/ros/galactic/lib/gazebo_ros/spawn_entity.py -entity waffle_pi -file /home/user/simulation_ws/install/turtlebot3_gazebo/share/turtlebot3_gazebo/models/turtlebot3_waffle_pi/model.sdf -x 0.0 -y 0.0 -z 0.01 --ros-args']

you can just abort the current command by pressing CTRL+C in the terminal, then run the last command “ros2 launch turtlebot3_gazebo empty_world.launch.py” again.

If everything loaded fine, you should have a Turtlebot Waffle PI simulation running:

Turtlebot Waffle PI - How to use ROS2 parameters

Turtlebot Waffle PI – How to use ROS2 parameters

Checking ROS2 Topics to ensure the simulation is ok

Now that our simulation is running, we can check the topics just to make sure everything loaded as expected.

For that, let’s open a second terminal and type the following command:

ros2 topic list

If you see the following list of topics, then everything has loaded fine:

user:~$ ros2 topic list

/camera/camera_info
/camera/image_raw
/clock
/cmd_vel
/imu
/joint_states
/odom
/parameter_events
/performance_metrics
/robot_description
/rosout
/scan
/tf
/tf_static

If you look carefully in the list of topics, we have the topic /cmd_vel. We are going to use it to send velocity commands to the robot using ROS parameters.

Understanding the parameter_tests package

So far so good. It is now time to check the structure of our workspace. Let’s start by opening our Code Editor:

Open the IDE - Code Editor

Open the IDE – Code Editor

After having the IDE open, under ros2_ws/src you should find a package named parameter_tests.

Inside that package, there is also a folder named parameter_tests with a file named parameter_tests_node.py. Please click on that file to open it and analyze its code.

The code is the following:

import rclpy
import rclpy.node
from rcl_interfaces.msg import ParameterDescriptor
from geometry_msgs.msg import Twist


class VelParam(rclpy.node.Node):
    def __init__(self):
        super().__init__('param_vel_node')
        self.timer = self.create_timer(0.1, self.timer_callback)
        self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
        self.msg = Twist()
        param_descriptor = ParameterDescriptor(
            description='Sets the velocity (in m/s) of the robot.')
        self.declare_parameter('velocity', 0.0, param_descriptor)
        # self.add_on_set_parameters_callback(self.parameter_callback)

    def timer_callback(self):
        my_param = self.get_parameter('velocity').value

        self.get_logger().info('Velocity parameter is: %f' % my_param)

        self.msg.linear.x = my_param
        self.publisher.publish(self.msg)

def main():
    rclpy.init()
    node = VelParam()
    rclpy.spin(node)


if __name__ == '__main__':
    main()

If you check the main function, we are basically starting the ROS node, instantiating an object of our VelParam class, and spinning that node.

On the VelParam, one of the most important parts is where we define the param_descriptor. That param descriptor is what we use to set a parameter called velocity and define its initial value as 0.0.

Another important part of the code is the timer:

self.timer = self.create_timer(0.1, self.timer_callback)

Every certain amount of time (0.1 sec) the timer_callback method is called.

If we now check that timer_callback method, we see that it basically reads the velocity parameter and uses it to publish a velocity to the /cmd_vel.

Running the parameter_tests_node node

Now that we understand what our node does, it is time to run it.

For that, let’s open a third terminal and run the following command:

ros2 run parameter_tests param_vel

You should see constant messages like the following:

[INFO] [1649095105.555241669] [param_vel_node]: Velocity parameter is: 0.000000
[INFO] [1649095105.559028822] [param_vel_node]: Velocity parameter is: 0.000000
[INFO] [1649095105.583306104] [param_vel_node]: Velocity parameter is: 0.000000
...

We see that the initial value of the velocity parameter is 0 (zero).

Checking ROS Parameters using the terminal

Ok, so far we see everything is working, and we were are able to set and retrieve the ROS Param using Python.

Now let’s list the parameters to identify our velocity param. Let’s open a third terminal and type ros2 param list. The output should be similar to the following:

user:~$ ros2 param list
/camera_driver:
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  update_rate
  use_sim_time
/gazebo:
  publish_rate
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time
/param_vel_node:
  use_sim_time
  velocity
/robot_state_publisher:
  ignore_timestamp
  publish_frequency
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  qos_overrides./tf.publisher.depth
  qos_overrides./tf.publisher.durability
  qos_overrides./tf.publisher.history
  qos_overrides./tf.publisher.reliability
  qos_overrides./tf_static.publisher.depth
  qos_overrides./tf_static.publisher.history
  qos_overrides./tf_static.publisher.reliability
  robot_description
  use_sim_time
  use_tf_static
/turtlebot3_diff_drive:
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  qos_overrides./tf.publisher.depth
  qos_overrides./tf.publisher.durability
  qos_overrides./tf.publisher.history
  qos_overrides./tf.publisher.reliability
  use_sim_time
/turtlebot3_imu:
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time
/turtlebot3_joint_state:
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time
/turtlebot3_laserscan:
  qos_overrides./clock.subscription.depth
  qos_overrides./clock.subscription.durability
  qos_overrides./clock.subscription.history
  qos_overrides./clock.subscription.reliability
  qos_overrides./parameter_events.publisher.depth
  qos_overrides./parameter_events.publisher.durability
  qos_overrides./parameter_events.publisher.history
  qos_overrides./parameter_events.publisher.reliability
  use_sim_time

 

The node we are interested in is the param_vel_node. In the output above, we can easily identify our velocity param there:

user:~$ ros2 param list
/param_vel_node:
  use_sim_time
  velocity

the use_sim_time is a parameter that comes in every node.

Moving the robot using ROS Parameter

That that we have the param_vel_node with the velocity param, we can easily set a value to that parameter with the following command:

ros2 param set /param_vel_node velocity 0.2

 

After running this command, you should see that the robot started moving.

If you also check the terminal where we launched our node, it should say the current value or our parameter:

[INFO] [1649096658.093696410] [param_vel_node]: Velocity parameter is: 0.200000
[INFO] [1649096658.181101399] [param_vel_node]: Velocity parameter is: 0.200000
[INFO] [1649096658.281628131] [param_vel_node]: Velocity parameter is: 0.200000

 

Remember that you can easily set the parameter to 0.0 again to stop the robot:

ros2 param set /param_vel_node velocity 0.0

Dumping ROS Parameters into a YAML file (YAML format)

Now that we saw that we can easily move the robot back and forth using ROS parameters, in case you need to somehow dump the parameters, you can easily do it with:

cd ~/ros2_ws/src/parameter_tests/config

 ros2 param dump /param_vel_node

The command will generate a file named ./param_vel_node.yaml with the following content (after we have set the velocity to 0.0 again):

/param_vel_node:
  ros__parameters:
    use_sim_time: false
    velocity: 0.0

 

Loading ROS Parameters when running Node using YAML files

All right, so far we have learned how to set parameters using Python and using the command line directly through ros2 param set. Now the time has come to also learn how to launch a node and set the parameters from a YAML file.

Before we do that, feel free to change the velocity value in the param_vel_node.yaml file.

Please, go to the second terminal where you launched the node and press CTRL+C to kill it. The simulation should be kept running.

Now, launch the node again, but now loading the parameters from the YAML file using the following command:

ros2 run parameter_tests param_vel --ros-args --params-file /home/user/ros2_ws/src/parameter_tests/config/param_vel_node.yaml

Based on the logs that are printed on the screen, you should be able to see that the parameters were correctly loaded from the YAML file.

Loading ROS Parameters using launch files directly

Up to now, we were launching our node using ros2 run, but if you are familiar with ROS, you may know that we can also use ros2 launch to launch ROS2 nodes.

If you check carefully the rosject, you will find a launch file under the following path:

~/ros2_ws/src/parameter_tests/launch/test_parameters.launch.py

It has the following content:

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='parameter_tests',
            executable='param_vel',
            name='param_vel_node',
            output='screen',
            emulate_tty=True,
            parameters=[
                {'velocity': 0.2}
            ]
        )
    ])

As you can see, we are setting here the velocity with parameters=[ {‘velocity’: 0.2}  ]

To launch that launch file, we run the following command:

ros2 launch parameter_tests test_parameters.launch.py

The robot should move again.

Congratulations, you now know all the basics about ROS Parameters in ROS2.

Youtube video

So this is the post for today. Remember that we have the live version of this post on YouTube. If you liked the content, please consider subscribing to our youtube channel. We are publishing new content ~every day.

Keep pushing your ROS Learning.

Related Courses & Training

If you want to learn more about ROS and ROS2, we recommend the following courses:

Pin It on Pinterest