Creating a Python Node from scratch (for :program:`ament_python`)
=================================================================

.. seealso::
         
          The official API documentation: https://docs.ros.org/en/jazzy/p/rclpy/rclpy.html


.. admonition:: **TL;DR** Making an :program:`ament_python` Node
         
         #. Modify :file:`package.xml` with any additional dependencies.
         #. Create the Node.
         #. Modify the :file:`setup.py` file.

Let us add an additional Node to our :program:`ament_python` package that actually uses ROS2 functionality. 
These are the steps that must be taken, in general, to add a new Node.

File structure
--------------

In this section, we will be modifying or creating the following files.

.. code-block:: console
    :emphasize-lines: 2,5,10

    python_package_with_a_node
    |-- package.xml
    |-- python_package_with_a_node
    |   |-- __init__.py
    |   |-- print_forever_node.py
    |   `-- sample_python_node.py
    |-- resource
    |   `-- python_package_with_a_node
    |-- setup.cfg
    |-- setup.py
    `-- test
        |-- test_copyright.py
        |-- test_flake8.py
        `-- test_pep257.py

.. _Handling dependencies:

Handling dependencies (:file:`package.xml`)
-------------------------------------------

The :file:`package.xml` was automatically generated by :program:`ros2 pkg create` and holds basic information about the package.

One important role of :file:`package.xml` is to declare dependencies with other ROS2 packages. It is common for new Nodes to have additional dependencies, so we will cover that here. For any ROS2 package, we must modify the :file:`package.xml` to add new dependencies.

In this toy example, let us add :code:`rclpy` as a dependency because it is the Python implementation of the :abbr:`RCL (ROS Client Library)`. All Nodes that use anything related to ROS2 will directly or indirectly depend on that library.

By no coincidence, the :file:`package.xml` has the :code:`.xml` extension, meaning that it is written in :abbr:`XML (Extensible Markup Language)`.

Let us add the dependency between the :code:`<license>` and :code:`<test_depend>` tags. This is not a strict requirement but is where it commonly is for standard packages.

:download:`package.xml <../../ros2_tutorial_workspace/src/python_package_with_a_node/package.xml>`

.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_node/package.xml
   :language: xml
   :linenos:
   :emphasize-lines: 10
  
Creating the Node
-----------------

In the directory :file:`src/python_package_with_a_node/python_package_with_a_node`, create a new file called :file:`print_forever_node.py`. Copy and paste the following contents into the file.

:download:`print_forever_node.py <../../ros2_tutorial_workspace/src/python_package_with_a_node/python_package_with_a_node/print_forever_node.py>`

.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_node/python_package_with_a_node/print_forever_node.py
   :language: python
   :linenos:
   :lines: 24-
   
.. _Making rosrun work:

Making :command:`ros2 run` work
-------------------------------

We need an additional step to make it deployable in a place where :command:`ros2 run` can find it.

To do so, we modify the :code:`console_scripts` key in the :code:`entry_points` dictionary defined in :file:`setup.py`, to have our new node, as follows

.. hint:: 

   :code:`console_scripts` expects a :code:`list` of :code:`str` in a specific format. Hence, follow the format properly and don't forget the commas to separate elements in the :code:`list`.

:download:`~/ros2_tutorial_workspace/src/python_package_with_a_node/setup.py <../../ros2_tutorial_workspace/src/python_package_with_a_node/setup.py>`

.. literalinclude:: ../../ros2_tutorial_workspace/src/python_package_with_a_node/setup.py
   :language: python
   :linenos:
   :emphasize-lines: 24

The format is straightforward, as follows

==================================   ===================================================================================
:code:`print_forever_node`           The name of the node when calling it through :command:`ros2 run`.
:code:`python_package_with_a_node`   The name of the package.
:code:`print_forever_node`           The name of the script, without the :file:`.py` extension.
:code:`main`                         The function, within the script, that will be called. In general, :code:`main`.
==================================   ===================================================================================

Build and source
----------------

We can build and source the workspace with the following command.

.. include:: the_canonical_build_command.rst

Test
----

And, with that, we can run

.. code-block:: console

  ros2 run python_package_with_a_node print_forever_node
   
which will output, as expected
 
.. code-block:: console
 
    [INFO] [1753518652.646459087] [print_forever]: Printed 0 times.
    [INFO] [1753518653.131078795] [print_forever]: Printed 1 times.
    [INFO] [1753518653.632436004] [print_forever]: Printed 2 times.
    [INFO] [1753518654.132090212] [print_forever]: Printed 3 times.
    [INFO] [1753518654.630924338] [print_forever]: Printed 4 times.

To stop, press :kbd:`CTRL+C` on the terminal and the Node will return gracefully.
