ROS Nodes

Creating nodes

Content Outline

    Motivation


    A toy example

    What is a Node


    ROS Node

    A node is a process that performs computation, and uses a client library to communicate with other nodes. Nodes are combined together into a graph and communicate with one another using streaming topics, RPC services, and the Parameter Server.

    Sources: About Nodes - ROS2 Docs , Nodes - ROS Wiki .

    Benefits of nodes:

    • Fault tolerance: as crashes are isolated to individual nodes (remember that nodes are individual processes).
    • Reduce code complexity: is reduced in comparison to monolithic systems.
    • Expose a minimal API: Implementation details are also well hidden for other nodes.
    • Language agnostic
    • Modularity
    • Code reuse

    Nodes can communicate with other nodes within:

    • the same process
    • in a different process
    • on a different machine.

    Important:

    Ideally each node should do one logical thing.

    Namespace: prefix to apply to entities associated with the node (node name, topics, etc).

    Creating a Bare Minimum Node


    A bare minimum node without any ROS entity.

    import rclpy
    
    def main(args=None) -> None:
    
        rclpy.init(args=args)
    
        node = rclpy.create_node("bare_node")
    
        rclpy.spin(node)
    
        rclpy.shutdown()
    
    
    if __name__ == "__main__":
        main()
    

    Remember that packages of type ament_python are structured like a standard python packages using setuptools . So, to make your node usable as a console script add an entry_points kwarg inside the setup() function within the setup.py file of your package. Check Setuptools - Entry Points for more details.

    setup.py
    ...
    
    entry_points={
        'console_scripts': [
            'bare = python_basics.bare:main',
        ],
    },
    
    ...

    Node entry points structure description.

    ENTRY_POINT_NAME = PACKAGE_NAME.MODULE_NAME:ENTRY_POINT

    Running Nodes


    You can run ROS nodes using the ROS cli with the following format:

    ros2 run <package_name> <node_entry_point_name>

    In this case:

    ros2 run python_basics bare

    erros?… here’s a list of common mistakes you may be making:

    • ros2 command not found (assuming you have ROS correctly installed using debian packages) Have you already source the underlying workspace? source /opt/ros/humble/setup.bash
    • Package 'python_basics' not found (assuming you have the package in the src folder in your workspace)
      • remember that every time you have new code files that you want to execute you must build the workspace, have you already done it? in the root directory of your workspace colcon build --symlink-install
      • have you already activated the overlaying workspace (your worksapce)? source ~/ros2_ws/install/local_setup.bash

    clearly not all possible error cases can be covered, so it is advisable to read the error message and try to understand it (I know, sometimes, just sometimes, error messages are not very useful).

    In case you haven’t wondered yet. You: “Wait!?, my node is python, why do I need to build my workspace if python is interpreted and does not require compiling.?”. Let me tell you that is an excellent question, colcon build does not only compile code, it has the potential to do many more things that we will not mention here (but you can see colcon docs ), in the case of ament_python packages colcon does not compile the code, but instead copies it from your package to the lib directory inside the install folder of the workspace. However this copy alone is not enough, you must source the workspace in a way that lets ROS know that there are new files in the install folder.

    The --symlink-install directive tells colcon not to perform a hard-copy (default behavior) but to perform a symlink of the files, this option is extremely useful when you are developing with python as it allows you to make changes to your file and because it is just a symbolic link the changes are automatically reflected from the file perspective in the install folder, which means that if you use --symlink-install you don’t have to do colcon build for every time you change the file (example bare.py), but you have to redo colcon build --symlink-install once every time there is a new file, this is for the purpose of creating the symlink of the new file. e.g. a new node called new.py and then you can just keep changing the file since the symlink already exist.

    The symlink concept is not exclusive for nodes, but also applies to any type of shared resource such as launch files, configurations, urdfs etc. Just keep in mind that currently (January 2024) there are no mechanisms to create symlinks for the resources specified in the setup.py section data_files of your ament_python. You can symlink data files in packages of type ament_python, thanks to the PR Implement a custom distutils command to symlink data_files #592

    Coding a Minimum Logger


    coding a logger (logger.py) in the package python_basics, code here

    import rclpy
    
    def main(args=None) -> None:
    
        rclpy.init(args=args)
    
        node = rclpy.create_node("logger_node")
    
        rate = node.create_rate(0.5)
        counter = 0
    
        while rclpy.ok():
            node.get_logger().info(f"hello {counter}")
    
            counter += 1
    
            rclpy.spin_once(node)
    
            rate.sleep()
    
    if __name__ == "__main__":
        main()
    

    Don’t forget to add the entry point in the setup.py: logger = python_basics.logger:main


    coding a logger using classes (class.py) in the package python_basics, code here

    import rclpy
    from rclpy.node import Node
    
    
    class MyNode(Node):
    
        def __init__(self) -> None:
            super().__init__("my_node")
    
            self.count = 0
    
            timer = self.create_timer(0.5, self.callback)
    
    
        def callback(self) -> None:
    
            self.get_logger().info(f"Hello {self.count}")
    
            self.count += 1
    
    
    def main(args=None) -> None:
    
        rclpy.init(args=args)
    
        node = MyNode()
    
        rclpy.spin(node)
    
        rclpy.shutdown()
    
    
    if __name__ == "__main__":
        main()
    

    Don’t forget to add the entry point in the setup.py: class = python_basics.class:main

    Capabilities


    The real power of nodes in ROS lies in their ability to contain various ROS entities:

    • Publisher: this entity allows a node to publish information.
    • Subscriber: allows a node to subscribe information.
    • Services
      • service client: allows a node to request a service (from a service server).
      • service server: allows a node to response to a request.
    • Actions
      • action client: allows a node to request an ACTION service.
      • action server: allows a node to response to an ACTION request.
    • Configurable parameters (run-time)

    A ROS node can be composed of none, one or several ROS entities, each with their respective interfaces. We will discuss each ros entity and their respective interfaces later in this serie of notes.

    External References