- https://docs.ros.org/en/foxy/Tutorials.html
- https://docs.ros2.org/latest/api/rclpy/index.html
- https://docs.ros.org/en/foxy/How-To-Guides/Launch-file-different-formats.html
- https://roboticsbackend.com/rclpy-params-tutorial-get-set-ros2-params-with-python
- ROS2 Cheat Sheet
- References
- 1. Environment
- 2. ROS2 Nodes
- 3. ROS2 Topics
- 4. ROS2 Services
- 5. ROS2 Parameters
- 6. ROS2 Actions
- 7. ROS2 Launch
- 8. ROS2 Bags
- 9. Creating a Workspace
- 10. ROS2 Packages
- 11. Creating a Package
- 12. Creating a Publisher and a Subscriber in the Same Node
- 13. Create Custom
msg
andsrv
Files - 14. Using ROS2 parameters in Python
Ubuntu 20.04 ROS2 Foxy
$ ros2 node list
$ ros2 node info <node_name>
$ ros2 run <package_name> <executable_name>
$ ros2 run <package_name> <executable_name> --ros-args --remap __node:=<my_node_name>
$ ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
If some topics don't show up in $ ros2 node list
use:
$ ros2 daemon stop
$ ros2 daemon start
$ ros2 topic list
$ ros2 topic list -t
$ ros2 topic list --show-types
$ ros2 topic info <topic_name>
$ ros2 topic info -v <topic_name>
$ ros2 topic info --verbose <topic_name>
$ ros2 topic type <topic_name>
$ ros2 topic hz <topic_name>
$ ros2 topic echo <topic_name>
$ ros2 topic echo --csv <topic_name>
$ ros2 topic pub <topic_name> <topic_type> <arguments>
If some topics don't show up in $ ros2 topic list
use:
$ ros2 daemon stop
$ ros2 daemon start
$ ros2 service list
$ ros2 service list -t
$ ros2 service list --show-types
$ ros2 service type <service_name>
$ ros2 interface show <pkg_name>/srv/<service_type>
$ ros2 interface show <service_type_name>.srv
Where: <pkg_name>/srv/<service_type> = <service_type_name>
$ ros2 service call <service_name> <service_type> <arguments>
$ ros2 param list
$ ros2 param describe <node_name> <parameter_name>
$ ros2 param get <node_name> <parameter_name>
$ ros2 param set <node_name> <parameter_name> <value>
$ ros2 param dump <node_name>
$ ros2 param load <node_name> <parameter_file>
$ ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
$ ros2 action list
$ ros2 action list -t
$ ros2 action list --show-types
$ ros2 action info <action_name>
$ ros2 interface show <pkg_name>/action/<action_type>
$ ros2 interface show <action_type_name>.action
Where: <pkg_name>/action/<action_type> = <action_type_name>
$ ros2 action send_goal <action_name> <action_type> <values>
$ ros2 action send_goal <action_name> <action_type> <values> --feedback
$ ros2 launch <pkg_name> <launch_file_name>.launch.py
$ ros2 launch <pkg_name> <launch_file_name>.launch.xml
$ ros2 launch <pkg_name> <launch_file_name>.launch.yaml
Using Python vs. XML vs. YAML Launch files
$ ros2 bag record <topic_name>
$ ros2 bag record -o <output_file_name> <topic_name1> <topic_name2> <topic_nameN>
$ ros2 bag record -o <output_file_name> -a
(all topics)
If output bag name is not provided, it take the pattern: rosbag2_year_month_day-hour_minute_second
$ ros2 bag info <bag_file_name>
$ ros2 bag play <bag_file_name>
- Source the environment:
$ source /opt/ros/$ROS_DISTRO/setup.bash
- Create a directory:
$ mkdir -p ~/dev_ws/src && cd dev_ws/src
- Clone a repo of package(s) OR Create a package(s) from scratch (follow along)
$ git clone https://github.com/ros/ros_tutorials.git -b foxy-devel
- Resolve Dependencies:
$ rosdep install -i --from-path src --rosdistro $ROS_DISTRO -y
- Build the workspace with colcon:
$ colcon build
-
--packages-up-to <pkg_name>
: builds the package you want, plus all its dependencies, not the whole workspace (to save time) -
--packages-select <pkg_name>
: build only the selected package(s) -
--symlink-install
: saves you from having to rebuild every time you tweak python scripts -
--event-handlers console_direct+
: shows console output while building (can otherwise be found in the log directory) NOTE : It’s good practice to runrosdep
in the root of your workspace (dev_ws
) to check for missing dependencies before building:# rosdep install -i --from-path <desired_path_to_check> --rosdistro $ROS_DISTRO $ rosdep install -i --from-path src --rosdistro foxy
-
- Source the underlay (if it's not already sourced), then source the overlay
- underlay: ROS2 installation
-
overlay: current development workspace
$ source /opt/ros/foxy/setup.bash # if not already existing in .bashrc $ cd ~/dev_ws $ . install/local_setup.bash
- You can modify and rebuild packages in the overlay separately from the underlay.
- The overlay takes precedence over the underlay in case of name conflicts.
- Modify the overlay
Modify$\longrightarrow$ Build (ifsrc
is modified)$\longrightarrow$ Source$\longrightarrow$ Run!
A ROS2 Package is a folder that contains your code in addition to specific files that define the folder as a package
- Python package
package.xml
file containing meta information about the packagesetup.py
containing instructions for how to install the packagesetup.cfg
is required when a package has executables, so ros2 run can find them/<package_name>
- a Python package with the same name as your ROS2 package, used by ROS2 tools to find your package, containsinit.py
- CMake package
package.xml
file containing meta information about the packageCMakeLists.txt
file that describes how to build the code within the package
$ cd ~/dev_ws/src
- Python
$ ros2 pkg create --build-type ament_python <package_name> # optional: package with a specific node name $ ros2 pkg create --build-type ament_python --node-name <my_node_name> <my_package_name>
- CMake
$ ros2 pkg create --build-type ament_cmake <package_name> # optional: package with a specific node name $ ros2 pkg create --build-type ament_cmake --node-name <my_node_name> <my_package_name>
- Create a package
$ cd dev_ws/src # ros2 pkg create --build-type {cmake,ament_cmake,ament_python} <package_name> $ ros2 pkg create --build-type ament_python py_pubsub
- Write the publisher and subscriber node
Edit the file to add the code 2.1. New Classes Method
# this directory is a Python package with the same name of the ROS2 package $ cd dev_ws/src/py_pubsub/py_pubsub $ touch publisher_subscriber_member_functions.py
2.2. Old School Method# import ROS Python Client Library import rclpy # import Node class from rclpy.node import Node # import string message type that will be used from std_msgs.msg import String # create a class that inherits Node class class MinimalPublisherSubscriber(Node): def __init__(self): # call the class' constructor and name it after my node super().__init__('minimal_publisher_subscriber') self.publisher_ = self.create_publisher(String, 'topic', 10) self.subscription = self.create_subscription( String, 'topic', self.listener_callback, 10) self.subscription # prevent unused variable warning timer_period = 0.5 # seconds # create a timer that executes the cb fcn every <timer_period> seconds self.timer = self.create_timer(timer_period, self.timer_callback) self.i = 0 def timer_callback(self): msg = String() msg.data = 'Hello World: %d' % self.i self.publisher_.publish(msg) self.get_logger().info('Publishing: "%s"' % msg.data) self.i += 1 def listener_callback(self, msg): self.get_logger().info('I heard: "%s"' % msg.data) def main(args=None): rclpy.init(args=args) minimal_publisher_subscriber = MinimalPublisherSubscriber() rclpy.spin(minimal_publisher_subscriber) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_publisher_subscriber.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
import rclpy from std_msgs.msg import String from time import sleep g_node = None def chatter_callback(msg): global g_node g_node.get_logger().info( 'I heard: "%s"' % msg.data) def main(args=None): global g_node rclpy.init(args=args) g_node = rclpy.create_node('minimal_subscriber') subscription = g_node.create_subscription(String, 'topic', chatter_callback, 10) subscription # prevent unused variable warning publisher = g_node.create_publisher(String, 'topic', 10) msg = String() i = 0 while rclpy.ok(): msg.data = 'Hello World: %d' % i i += 1 g_node.get_logger().info('Publishing: "%s"' % msg.data) publisher.publish(msg) sleep(0.5) # seconds => 2 Hz # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) g_node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
- Add an Entry Point
- Navigate to your ROS2 package
$ cd dev_ws/src/py_pubsub
- Open
setup.py
and add the entry point for the node, the name of the entry point will be used to run the node laterentry_points={ 'console_scripts': [ 'talker_listener = py_pubsub.publisher_subscriber_member_functions:main', ], },
- Navigate to your ROS2 package
- Build and Run
- Navigate to the root of your workspace
$ cd dev_ws
- Resolve dependencies
It's good practice to run
rosdep
in the root of your workspace(dev_ws)
to check for missing dependencies before building$ rosdep install -i --from-path src --rosdistro foxy -y
- Build
$ colcon build --packages-select py_pubsub
- Source the setup files
$ . install/setup.bash
- Run
The output should look like this ...
$ ros2 run py_pubsub talker_listener
... [INFO] [1658726617.443270804] [minimal_publisher_subscriber]: Publishing: "Hello World: 2" [INFO] [1658726617.444563242] [minimal_publisher_subscriber]: I heard: "Hello World: 2" [INFO] [1658726617.943327820] [minimal_publisher_subscriber]: Publishing: "Hello World: 3" [INFO] [1658726617.944592867] [minimal_publisher_subscriber]: I heard: "Hello World: 3" [INFO] [1658726618.443164429] [minimal_publisher_subscriber]: Publishing: "Hello World: 4" [INFO] [1658726618.444557197] [minimal_publisher_subscriber]: I heard: "Hello World: 4" [INFO] [1658726618.943250358] [minimal_publisher_subscriber]: Publishing: "Hello World: 5" [INFO] [1658726618.944787046] [minimal_publisher_subscriber]: I heard: "Hello World: 5" [INFO] [1658726618.943250358] [minimal_publisher_subscriber]: Publishing: "Hello World: 6" [INFO] [1658726618.944787046] [minimal_publisher_subscriber]: I heard: "Hello World: 6" ...
- Navigate to the root of your workspace
-
Create a CMake package
- There currently isn’t a way to generate a
.msg
or.srv
file in a pure Python package. You can create a custom interface in a CMake package, and then use it in a Python node$ ros2 pkg create --build-type ament_cmake tutorial_interfaces
- As a good practice, keep
.msg
and.srv
files in their own directories within a package. Create the directories indev_ws/src/tutorial_interfaces
:$ mkdir msg $ mkdir srv
- There currently isn’t a way to generate a
-
Create custom definitions
-
msg
definitionEdit the$ cd dev_ws/src/tutorial_interfaces/msg $ touch Num.msg
Num.msg
file to add only one line of code declaring its data structure:int64 num
-
srv
definitionEdit the$ cd dev_ws/src/tutorial_interfaces/srv $ touch AddThreeInts.srv
AddThreeInts.srv
file to add the following request and response structure:This is your custom service that requests three integers named a, b, and c, and responds with an integer called sum.int64 a int64 b int64 c --- int64 sum
-
-
Edit
CMakeLists.txt
To convert the interfaces you defined into language-specific code (like C++ and Python) so that they can be used in those languages, add the following lines toCMakeLists.txt
:find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/Num.msg" "srv/AddThreeInts.srv" )
NOTE : The library name must match
${PROJECT_NAME}
TODO : using the newly generated messages and services
We will use the code from Creating a Publisher and a Subsciber to add some parameters
- New Classes Method
# import ROS Python Client Library import rclpy # import Node class from rclpy.node import Node # import string message type that will be used from std_msgs.msg import String # create a class that inherits Node class class MinimalPublisherSubscriber(Node): def __init__(self): # call the class' constructor and name it after my node super().__init__('minimal_publisher_subscriber') self.publisher_ = self.create_publisher(String, 'topic', 10) self.subscription = self.create_subscription( String, 'topic', self.listener_callback, 10) self.subscription # prevent unused variable warning timer_period = 0.5 # seconds # create a timer that executes the cb fcn every <timer_period> seconds self.timer = self.create_timer(timer_period, self.timer_callback) self.i = 0 # declare new parameters self.declare_parameter('my_str', 'default_value') self.declare_parameter('my_int', 7) self.declare_parameter('my_double_array', 1.253) # get parameters' values param_str = self.get_parameter('my_str').value param_int = self.get_parameter('my_int').value param_double_array = self.get_parameter('my_double_array').value self.get_logger().info("str: %s, int: %s, double[]: %s" % (str(param_str.value), str(param_int.value), str(param_double_array.value),)) def timer_callback(self): msg = String() msg.data = 'Hello World: %d' % self.i self.publisher_.publish(msg) self.get_logger().info('Publishing: "%s"' % msg.data) self.i += 1 def listener_callback(self, msg): self.get_logger().info('I heard: "%s"' % msg.data) def main(args=None): rclpy.init(args=args) minimal_publisher_subscriber = MinimalPublisherSubscriber() rclpy.spin(minimal_publisher_subscriber) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_publisher_subscriber.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
- Old School Method
import rclpy from std_msgs.msg import String from time import sleep g_node = None def chatter_callback(msg): global g_node g_node.get_logger().info( 'I heard: "%s"' % msg.data) def main(args=None): global g_node rclpy.init(args=args) g_node = rclpy.create_node('minimal_subscriber') subscription = g_node.create_subscription(String, 'topic', chatter_callback, 10) subscription # prevent unused variable warning publisher = g_node.create_publisher(String, 'topic', 10) msg = String() # declare new parameters g_node.declare_parameter('my_str', 'default_value') g_node.declare_parameter('my_int', 7) g_node.declare_parameter('my_double_array', 1.253) # get parameters' values param_str = g_node.get_parameter('my_str').value param_int = g_node.get_parameter('my_int').value param_double_array = g_node.get_parameter('my_double_array').value g_node.get_logger().info("str: %s, int: %s, double[]: %s" % (str(param_str.value), str(param_int.value), str(param_double_array.value),)) i = 0 while rclpy.ok(): msg.data = 'Hello World: %d' % i i += 1 g_node.get_logger().info('Publishing: "%s"' % msg.data) publisher.publish(msg) sleep(0.5) # seconds rclpy.spin_once(g_node) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) g_node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()