Using a Python Library from another package (for ament_python)

Let us create a package with a Node that uses the library we created in the prior example.

Note that we must add the python_package_with_a_library as a dependency to our new package. The easiest way to do so is through ros2 pkg create. We also add rclcpp as a dependency so that our Node can do something useful.

cd ~/ros2_tutorial_workspace/src
ros2 pkg create python_package_that_uses_the_library \
--dependencies rclpy python_package_with_a_library \
--build-type ament_python \
--node-name node_that_uses_the_library

resulting in yet another version of our favorite wall of text

going to create a new package
package name: python_package_that_uses_the_library
destination directory: /home/murilo/ros2_tutorial_workspace/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['murilo <murilomarinho@ieee.org>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy', 'python_package_with_a_library']
node_name: node_that_uses_the_library
creating folder ./python_package_that_uses_the_library
creating ./python_package_that_uses_the_library/package.xml
creating source folder
creating folder ./python_package_that_uses_the_library/python_package_that_uses_the_library
creating ./python_package_that_uses_the_library/setup.py
creating ./python_package_that_uses_the_library/setup.cfg
creating folder ./python_package_that_uses_the_library/resource
creating ./python_package_that_uses_the_library/resource/python_package_that_uses_the_library
creating ./python_package_that_uses_the_library/python_package_that_uses_the_library/__init__.py
creating folder ./python_package_that_uses_the_library/test
creating ./python_package_that_uses_the_library/test/test_copyright.py
creating ./python_package_that_uses_the_library/test/test_flake8.py
creating ./python_package_that_uses_the_library/test/test_pep257.py
creating ./python_package_that_uses_the_library/python_package_that_uses_the_library/node_that_uses_the_library.py

[WARNING]: Unknown license 'TODO: License declaration'.  This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0

The sample Node

Given that it was created from a template, the file python_package_that_uses_the_library/python_package_that_uses_the_library/node_that_uses_the_library.py is currently mostly empty. Let us replace its contents with

node_that_uses_the_library.py

 1import rclpy
 2from rclpy.node import Node
 3from python_package_with_a_library.sample_python_library import SampleClass, sample_function_for_square_of_sum
 4
 5
 6class NodeThatUsesTheLibrary(Node):
 7    """A ROS2 Node that prints to the console periodically."""
 8
 9    def __init__(self):
10        super().__init__('node_that_uses_the_library')
11        timer_period: float = 0.5
12        self.timer = self.create_timer(timer_period, self.timer_callback)
13
14    def timer_callback(self):
15        """
16        Method that is periodically called by the timer.
17        Prints out the result of sample_function_for_square_of_sum of two random numbers,
18        followed by the result of SampleClass.get_name() for an instance created with
19        a ten-character-long ascii string of random characters.
20        """
21        a: float = random.uniform(0, 1)
22        b: float = random.uniform(1, 2)
23        c: float = sample_function_for_square_of_sum(a, b)
24        self.get_logger().info(f'sample_function_for_square_of_sum({a},{b}) returned {c}.')
25
26        random_name_ascii: str = ''.join(random.choice(string.ascii_letters) for _ in range(10))
27        sample_class_with_random_name = SampleClass(name=random_name_ascii)
28        self.get_logger().info(f'sample_class_with_random_name.get_name() '
29                               f'returned {sample_class_with_random_name.get_name()}.')
30
31
32def main(args=None):
33    """
34    The main function.
35    :param args: Not used directly by the user, but used by ROS2 to configure
36    certain aspects of the Node.
37    """
38    try:
39        rclpy.init(args=args)
40
41        node_that_uses_the_library = NodeThatUsesTheLibrary()
42
43        rclpy.spin(node_that_uses_the_library)
44    except KeyboardInterrupt:
45        pass
46    except Exception as e:
47        print(e)
48
49
50if __name__ == '__main__':
51    main()

Indeed, the most difficult part is to make and configure the library itself. After that, to use it in another package, it is straightforward. We import the library.

import rclpy
from rclpy.node import Node
from python_package_with_a_library.sample_python_library import SampleClass, sample_function_for_square_of_sum

And then use the symbols we imported as we would with any other Python library.

    def timer_callback(self):
        """
        Method that is periodically called by the timer.
        Prints out the result of sample_function_for_square_of_sum of two random numbers,
        followed by the result of SampleClass.get_name() for an instance created with
        a ten-character-long ascii string of random characters.
        """
        a: float = random.uniform(0, 1)
        b: float = random.uniform(1, 2)
        c: float = sample_function_for_square_of_sum(a, b)
        self.get_logger().info(f'sample_function_for_square_of_sum({a},{b}) returned {c}.')

        random_name_ascii: str = ''.join(random.choice(string.ascii_letters) for _ in range(10))
        sample_class_with_random_name = SampleClass(name=random_name_ascii)
        self.get_logger().info(f'sample_class_with_random_name.get_name() '
                               f'returned {sample_class_with_random_name.get_name()}.')

Build and source

As always, this is needed so that our new package and node can be recognized by ros2 run.

cd ~/ros2_tutorial_workspace
colcon build
source install/setup.bash

Note

If you don’t remember why we’re building with these commands, see Always source after you build.

Run

Hint

Remember that you can stop the node at any time with CTRL+C.

ros2 run python_package_that_uses_the_library node_that_uses_the_library

Which outputs something similar to the shown below, but with different numbers and strings as they are randomized.

[INFO] [1683598288.149167944] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.19395834493833486,1.3891603395040568) returned 2.506264769030609.
[INFO] [1683598288.149643378] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned qyOXLBEtzZ.
[INFO] [1683598288.616095880] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.7387236329957096,1.7650481260672202) returned 6.2688730214810775.
[INFO] [1683598288.616604769] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned LCFNFyzwhk.
[INFO] [1683598289.116050219] [node_that_uses_the_library]: sample_function_for_square_of_sum(0.003813494022560704,1.7056916575839387) returned 2.9224078633691604.
[INFO] [1683598289.116553899] [node_that_uses_the_library]: sample_class_with_random_name.get_name() returned wrtTlOdanZ.