From 4ea1fd3c635b57cdd1dfcaf138464637bb758875 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 7 Sep 2023 11:39:47 -0300 Subject: [PATCH 1/5] Improve launch documentation Signed-off-by: Michel Hidalgo --- source/Concepts/Basic/About-Launch.rst | 36 +- .../Launch-file-different-formats.rst | 323 +++++++++++++++++- 2 files changed, 343 insertions(+), 16 deletions(-) diff --git a/source/Concepts/Basic/About-Launch.rst b/source/Concepts/Basic/About-Launch.rst index 75a12f4d97d..80f009109b8 100644 --- a/source/Concepts/Basic/About-Launch.rst +++ b/source/Concepts/Basic/About-Launch.rst @@ -4,15 +4,33 @@ Launch .. contents:: Table of Contents :local: -A ROS 2 system typically consists of many nodes running across many different processes (and even different machines). -While it is possible to run each of these nodes separately, it gets cumbersome quite quickly. +Overview +-------- -The launch system in ROS 2 is meant to automate the running of many nodes with a single command. -It helps the user describe the configuration of their system and then executes it as described. -The configuration of the system includes what programs to run, where to run them, what arguments to pass them, and ROS-specific conventions which make it easy to reuse components throughout the system by giving them each a different configuration. -It is also responsible for monitoring the state of the processes launched, and reporting and/or reacting to changes in the state of those processes. +A ROS 2 system typically consists of multiple nodes running across different processes (and even different machines). +While it is possible to run each of these nodes separately, it can get cumbersome rather quickly. -All of the above is specified in a launch file, which can be written in Python, XML, or YAML. -This launch file can then be run using the ``ros2 launch`` command, and all of the nodes specified will be run. +The purpose of the ``launch`` system in ROS 2 is to provide the means to orchestrate the execution of such a system. This tool uses _launch files_ to describe the system and its configuration, but not limited to which programs to run, how to run them and which arguments to use for them. These descriptions are then executed, starting processes, monitoring them, reporting on their state, and even reacting to their behavior. -The `design document `__ details the goal of the design of ROS 2's launch system (not all functionality is currently available). +System descriptions +------------------- + +``launch`` works with _descriptions_, which, for the most part, amount to sequences of _actions_ executing in a _context_ or scope. All actions have side effects, which may be limited to the context in which they are running or extend to the larger execution environment (e.g. the local operating system). These side effects will typically outlive the context in which the corresponding action was run, which may be obvious for an action that changes an environment variable but not for an action that executes a process. Actions may also generate _events_, signals that are propagated through the context, that may be handled by _event handlers_. + +Actions alone allow for static descriptions that act upon and react to the environment in predefined ways. For a description to adapt to external input, _conditions_ and _substitutions_ exist. The former are boolean predicates, used for conditional actions, while the latter resemble text macros, useful for dynamic action configuration. + +In a way, ``launch`` descriptions are not that different from ASTs in procedural programming languages. The analogy brings about an important distinction that is easy to miss when writing launch files using the native Python APIs: neither actions nor conditions nor substitutions are computations but descriptions of computations to be carried out on launch. That is how actions can be implemented as sequences of other actions -- launch file inclusion being the paradigmatic example of this. + +For day to day use, a simple mental model to reason about these descriptions is one of two phases: a configuration phase, up until the description is fully constructed, and an execution phase, during which the description is acted on and until ``launch`` shuts down. + +Practical aspects +----------------- + +Launch files can be written in Python, XML, or YAML, and run using the ``ros2 launch`` command. Launch files can include other launch files, written in any of the supported languages, for modular system description and component reuse. Context in launch is in general not restricted to syntactical boundaries (e.g. files, groups) -- scoping is an explicit operation (see push / pop primitives), making variable propagation across launch file hierarchies the default. Launch files can take arguments which are put in context, making them available to substitutions. + +Check the :doc:`../../How-To-Guides/Launch-file-different-formats` guide on how to write launch files. + +References +---------- + +The most thorough reference on the design of ``launch`` is, unsurprisingly, its seminal `design document `__ (which even includes functionality not yet available). [``launch`` documentation](https://docs.ros.org/en/rolling/p/launch`` complements it, detailing the architecture of the core Python library. For everything else, both ``launch`` and ``launch_ros`` APIs are documented. diff --git a/source/How-To-Guides/Launch-file-different-formats.rst b/source/How-To-Guides/Launch-file-different-formats.rst index 4cbb7776a19..a9ea1bd1ee2 100644 --- a/source/How-To-Guides/Launch-file-different-formats.rst +++ b/source/How-To-Guides/Launch-file-different-formats.rst @@ -10,12 +10,321 @@ Using Python, XML, and YAML for ROS 2 Launch Files :local: ROS 2 launch files can be written in Python, XML, and YAML. -This guide shows how to use these different formats to accomplish the same task, as well as has some discussion on when to use each format. +This guide shows how to use these different formats to accomplish the same task. It also discusses when to use each format. -Launch file examples --------------------- +Examples +-------- + +Taking arguments +^^^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Declares arguments with and without defaults. +* Logs a message that interpolates those arguments. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import LogInfo + from launch.substitutions import LaunchConfiguration + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('who'), + DeclareLaunchArgument('where', default_value='home'), + + LogInfo(msg=[LaunchConfiguration('who'), ' is at ', LaunchConfiguration('where')]) + ]) + + + .. group-tab:: XML + + .. code-block:: xml + + + + + + + + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # example_launch.yaml + + launch: + - arg: + name: "who" + - arg: + name: "where" + default: "home" + - log: + message: "$(var who) is at $(var where)" + +Requiring processes +^^^^^^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Declares an argument with defaults. +* Sleeps for some time, then shuts down launch. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import ExecuteProcess + from launch.actions import LogInfo + from launch.actions import Shutdown + from launch.substitutions import LaunchConfiguration + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('duration', default_value='10'), + ExecuteProcess( + cmd=['sleep', LaunchConfiguration('duration')], + on_exit=[ + LogInfo(msg='Done sleeping!'), + Shutdown(reason='main process terminated') + ] + ), + LogInfo(msg=['Sleeping for ', LaunchConfiguration('duration'), ' seconds']), + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # Currently unsupported + +Replicating hierarchies +^^^^^^^^^^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Declares an argument without defaults. +* Generates namespaced groups iteratively based on that argument. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import GroupAction + from launch.actions import OpaqueFunction + from launch.substitutions import LaunchConfiguration + from launch_ros.actions import Node + from launch_ros.actions import PushRosNamespace + + def generate_turtles_description(context, turtles): + return [ + GroupAction( + actions=[ + PushRosNamespace(turtle_name), + Node( + package='turtlesim', + executable='turtlesim_node', + output='screen') + ] + ) + for turtle_name in turtles.perform(context).split() + ] + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('turtles'), + OpaqueFunction( + function=generate_turtles_description, + args=[LaunchConfiguration('turtles')]) + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # Currently unsupported + +Cleaning after +^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Declares an argument without defaults. +* Registers an action to execute on shutdown. +* Forces shutdown after a time specified by the argument. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import ExecuteProcess + from launch.actions import RegisterEventHandler + from launch.actions import Shutdown + from launch.actions import TimerAction + from launch.event_handlers import OnShutdown + from launch.substitutions import LaunchConfiguration + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('timeout'), + RegisterEventHandler(OnShutdown(on_shutdown=[ + ExecuteProcess(cmd=['rm', '-f', '/tmp/resource']), + ])), + ExecuteProcess(cmd=['touch', '/tmp/resource']), + TimerAction(period=LaunchConfiguration('timeout'), actions=[ + Shutdown(reason='launch timed out!') + ]) + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # Currently unsupported + +Managing lifecycles +^^^^^^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Registers a custom event handler to manage lifecycles synchronously. +* Starts talker and listener managed nodes. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_launch.py + + from launch import LaunchDescription + from launch.actions import RegisterEventHandler + from launch.actions import EmitEvent + from launch.event_handler import BaseEventHandler + from launch.events import Shutdown + from launch.events import matches_action + from launch.events.process import ProcessStarted + from launch_ros.actions import LifecycleNode + from launch_ros.events.lifecycle import ChangeState + from launch_ros.events.lifecycle import StateTransition + + import lifecycle_msgs.msg + + class LifecycleManager(BaseEventHandler): + + BRINGUP_SEQUENCE = { + lifecycle_msgs.msg.State.PRIMARY_STATE_UNCONFIGURED: lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE, + lifecycle_msgs.msg.State.PRIMARY_STATE_INACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE + } + + SHUTDOWN_SEQUENCE = { + lifecycle_msgs.msg.State.PRIMARY_STATE_UNCONFIGURED: lifecycle_msgs.msg.Transition.TRANSITION_UNCONFIGURED_SHUTDOWN, + lifecycle_msgs.msg.State.PRIMARY_STATE_INACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_INACTIVE_SHUTDOWN, + lifecycle_msgs.msg.State.PRIMARY_STATE_ACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_ACTIVE_SHUTDOWN + } + + def __init__(self, nodes): + self.managees = {node: lifecycle_msgs.msg.State.PRIMARY_STATE_UNKNOWN for node in nodes} + matcher = (lambda event: ( + isinstance(event, (ProcessStarted, StateTransition)) and event.action in self.managees)) + super().__init__(matcher=matcher) + + def handle(self, event, context): + if isinstance(event, ProcessStarted): + self.managees[event.action] = lifecycle_msgs.msg.State.PRIMARY_STATE_UNCONFIGURED + if isinstance(event, StateTransition): + self.managees[event.action] = event.msg.goal_state.id + states = list(set(self.managees.values())) + common_state = states[0] if len(states) == 1 else None + if common_state in self.BRINGUP_SEQUENCE: + matcher = lambda action: action in self.managees + return [ + EmitEvent(event=ChangeState( + lifecycle_node_matcher=matcher, + transition_id=self.BRINGUP_SEQUENCE[common_state]))] + return None + + def generate_launch_description(): + first_talker_node = LifecycleNode( + package='lifecycle', executable='lifecycle_talker', + name='first_talker', namespace='', output='screen') + second_talker_node = LifecycleNode( + package='lifecycle', executable='lifecycle_talker', + name='second_talker', namespace='', output='screen') + listener_node = LifecycleNode( + package='lifecycle', executable='lifecycle_listener', + name='listener', namespace='', output='screen', + remappings=[('/lc_talker/transition_event', + '/first_talker/transition_event')]) + manager = LifecycleManager([first_talker_node, second_talker_node]) + return LaunchDescription([ + RegisterEventHandler(manager), + first_talker_node, second_talker_node, listener_node + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # Currently unsupported + +Launching many nodes +^^^^^^^^^^^^^^^^^^^^ -Below is a launch file implemented in Python, XML, and YAML. Each launch file performs the following actions: * Setup command line arguments with defaults @@ -315,7 +624,7 @@ Each launch file performs the following actions: from: "/output/cmd_vel" to: "/turtlesim2/turtle1/cmd_vel" -Using the Launch files from the command line +Using launch files from the command line -------------------------------------------- Launching @@ -370,9 +679,9 @@ Python, XML, or YAML: Which should I use? For most applications the choice of which ROS 2 launch format comes down to developer preference. However, if your launch file requires flexibility that you cannot achieve with XML or YAML, you can use Python to write your launch file. -Using Python for ROS 2 launch is more flexible because of following two reasons: +Using Python for ROS 2 launch is more flexible because: * Python is a scripting language, and thus you can leverage the language and its libraries in your launch files. -* `ros2/launch `_ (general launch features) and `ros2/launch_ros `_ (ROS 2 specific launch features) are written in Python and thus you have lower level access to launch features that may not be exposed by XML and YAML. +* `ros2/launch `_ (general launch features) and `ros2/launch_ros `_ (ROS 2 specific launch features) are written in Python and thus you have lower level access to launch features that may not be yet exposed via XML and YAML. That being said, a launch file written in Python may be more complex and verbose than one in XML or YAML. From 0f4d33c63749da37817b34e86d5f1495df3e6196 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Mon, 18 Sep 2023 13:21:56 -0300 Subject: [PATCH 2/5] Rework launch concepts page Signed-off-by: Michel Hidalgo --- source/Concepts/Basic/About-Launch.rst | 44 ++++++++++++++++++-------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/source/Concepts/Basic/About-Launch.rst b/source/Concepts/Basic/About-Launch.rst index 80f009109b8..4c529915c2a 100644 --- a/source/Concepts/Basic/About-Launch.rst +++ b/source/Concepts/Basic/About-Launch.rst @@ -7,30 +7,48 @@ Launch Overview -------- -A ROS 2 system typically consists of multiple nodes running across different processes (and even different machines). -While it is possible to run each of these nodes separately, it can get cumbersome rather quickly. +A ROS 2 system typically consists of multiple :doc:`nodes ` running across different processes (and even different machines). +While it is possible to start each of these nodes manually, it can get cumbersome rather quickly. +Automation is also non-trivial, even if existing tools for generic process control and configuration management are leveraged, let alone crafting it from first principles (e.g. proper OS process management using shell scripts or general purpose system programming languages can be tricky on its own). -The purpose of the ``launch`` system in ROS 2 is to provide the means to orchestrate the execution of such a system. This tool uses _launch files_ to describe the system and its configuration, but not limited to which programs to run, how to run them and which arguments to use for them. These descriptions are then executed, starting processes, monitoring them, reporting on their state, and even reacting to their behavior. +``launch`` provides the means to orchestrate the execution of ROS 2 systems, in ROS 2 terms. +_Launch files_ describe the orchestration procedure, including but not limited to which nodes to run, how to run them, and which arguments to use for them. +These descriptions are then executed, starting processes, monitoring them, reporting on their state, and even reacting to their behavior. -System descriptions -------------------- +Moving parts +------------ -``launch`` works with _descriptions_, which, for the most part, amount to sequences of _actions_ executing in a _context_ or scope. All actions have side effects, which may be limited to the context in which they are running or extend to the larger execution environment (e.g. the local operating system). These side effects will typically outlive the context in which the corresponding action was run, which may be obvious for an action that changes an environment variable but not for an action that executes a process. Actions may also generate _events_, signals that are propagated through the context, that may be handled by _event handlers_. +``launch`` works with _actions_, abstract representations of computational procedures with side effects on its execution environment. Logging to standard output, executing a program, shutting down ``launch`` itself, are examples of actions. An action may also encompass another action or a collection thereof to describe more complex functionality. A collection of actions makes up the _description_ of a procedure that ``launch`` can take and execute. These descriptions are typically read from source files, so called _launch files_, which may be written in Python, or using specific XML or YAML syntax. -Actions alone allow for static descriptions that act upon and react to the environment in predefined ways. For a description to adapt to external input, _conditions_ and _substitutions_ exist. The former are boolean predicates, used for conditional actions, while the latter resemble text macros, useful for dynamic action configuration. +While the extent of the execution environment of an action depends on its nature e.g. logging to standard output is circumscribed to the ``launch`` process whereas executing a program reaches out to the host operating system, ``launch`` maintains an execution _context_ in which these actions take place and through which these can interact between them and with the user. +The ``launch`` context holds _configuration variables_ (i.e. state) and propagates _events_, both of which are available to actions and to ``launch`` itself. +Events are signals that originate internally in actions or externally on user input. It is through events, for example, that ``launch`` description execution (i.e. inclusion) is triggered. +Configuration variables populate the ``launch`` context. It is through configuration variables, for example, that ``launch`` descriptions can take arguments before execution. -In a way, ``launch`` descriptions are not that different from ASTs in procedural programming languages. The analogy brings about an important distinction that is easy to miss when writing launch files using the native Python APIs: neither actions nor conditions nor substitutions are computations but descriptions of computations to be carried out on launch. That is how actions can be implemented as sequences of other actions -- launch file inclusion being the paradigmatic example of this. +In connection with its context, ``launch`` defines _conditions_, _substitutions_, and _event handlers_ to broaden its scope of application. +The first two are different forms of expressions evaluated in ``launch`` context (though they may also tap into the larger execution environment): conditions are boolean predicates, used to define conditional actions, while substitutions are string interpolation expressions that enable dynamic ``launch`` descriptions. +Event handlers, on the other hand, are collections of actions registered for execution when and if the corresponding event occurs. -For day to day use, a simple mental model to reason about these descriptions is one of two phases: a configuration phase, up until the description is fully constructed, and an execution phase, during which the description is acted on and until ``launch`` shuts down. +As it may be apparent by now, ``launch`` descriptions are essentially programs in a domain specific language tailored for process orchestration, and, in particular, ROS 2 system orchestration. When composed using the building blocks available in its native Python implementation, these descriptions resemble `ASTs `_ in procedural programming languages. The analogy has its limits: context is not implicitly restricted to syntactical boundaries like it would for typical variable scopes, and action execution is naturally concurrent as opposed to sequential, to name a few. However, it does bring about an important distinction that is easy to miss when writing launch files in Python: no action nor condition nor substitution carries out a computation upon instantiation but simply specifies a computation to be carried out in runtime. + +.. note:: + + A simple mental model to reason about Python launch files is one of two phases: a configuration phase, during which the description is fully constructed, and an execution phase, during which ``launch`` executes based on the provided description until shutdown. Practical aspects ----------------- -Launch files can be written in Python, XML, or YAML, and run using the ``ros2 launch`` command. Launch files can include other launch files, written in any of the supported languages, for modular system description and component reuse. Context in launch is in general not restricted to syntactical boundaries (e.g. files, groups) -- scoping is an explicit operation (see push / pop primitives), making variable propagation across launch file hierarchies the default. Launch files can take arguments which are put in context, making them available to substitutions. - -Check the :doc:`../../How-To-Guides/Launch-file-different-formats` guide on how to write launch files. +* Launch files can be written in Python, XML, or YAML. + XML and YAML launch files keep it simple and avoid the confusion that a domain specific language embedded in a general purpose programming language may bring, but the flexibility and complexity that Python launch files afford may sometimes be useful if not necessary. + Refer to the examples in the :doc:`../../How-To-Guides/Launch-file-different-formats` guide on how to write launch files. +* Launch files can include other launch files, written in any of the supported languages, for modular system description and component reuse. +* Launch files, written in any of the supported languages, can be run using the ``ros2 launch`` command, which also can take their arguments. + Note the difference with the ``ros2 run`` command, which works with executables installed by ROS 2 packages, not launch files. +* Most of ``launch`` infrastructure lives in the ``launch`` Python package, but ROS 2 specifics live in the ``launch_ros`` Python package. References ---------- -The most thorough reference on the design of ``launch`` is, unsurprisingly, its seminal `design document `__ (which even includes functionality not yet available). [``launch`` documentation](https://docs.ros.org/en/rolling/p/launch`` complements it, detailing the architecture of the core Python library. For everything else, both ``launch`` and ``launch_ros`` APIs are documented. +The most thorough reference on the design of ``launch`` is, unsurprisingly, its seminal `design document `__ (which even includes functionality not yet available). +[``launch`` documentation](https://docs.ros.org/en/rolling/p/launch`` complements it, detailing the architecture of the core Python library. +For everything else, both ``launch`` and ``launch_ros`` APIs are documented. From c3928aea12ff8300b1767476ea3d77bfb390b552 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Tue, 19 Sep 2023 17:25:01 -0300 Subject: [PATCH 3/5] Augment launch file examples Signed-off-by: Michel Hidalgo --- .../Launch-file-different-formats.rst | 615 ++++++++++++++++-- 1 file changed, 550 insertions(+), 65 deletions(-) diff --git a/source/How-To-Guides/Launch-file-different-formats.rst b/source/How-To-Guides/Launch-file-different-formats.rst index a9ea1bd1ee2..816e5e03029 100644 --- a/source/How-To-Guides/Launch-file-different-formats.rst +++ b/source/How-To-Guides/Launch-file-different-formats.rst @@ -10,10 +10,46 @@ Using Python, XML, and YAML for ROS 2 Launch Files :local: ROS 2 launch files can be written in Python, XML, and YAML. -This guide shows how to use these different formats to accomplish the same task. It also discusses when to use each format. +This guide shows how to use these different formats to accomplish the same task. +It also discusses when to use each format. -Examples --------- +Primer on running launch files +------------------------------ + +Launching +^^^^^^^^^ + +Any of the example launch files can be run with ``ros2 launch``. +To try them locally, you can either create a new package and use + +.. code-block:: console + + ros2 launch + +or run the file directly by specifying the path to the launch file + +.. code-block:: console + + ros2 launch + +Setting arguments +^^^^^^^^^^^^^^^^^ + +To set the arguments that are passed to the launch file, you should use ``key:=value`` syntax. +For example, you can set the value of argument ``arg`` in the following way: + +.. code-block:: console + + ros2 launch arg:=value + +or + +.. code-block:: console + + ros2 launch arg:=value + +Example launch files +-------------------- Taking arguments ^^^^^^^^^^^^^^^^ @@ -21,7 +57,7 @@ Taking arguments Each launch file performs the following actions: * Declares arguments with and without defaults. -* Logs a message that interpolates those arguments. +* Logs a message that uses those arguments. .. tabs:: @@ -29,7 +65,7 @@ Each launch file performs the following actions: .. code-block:: python - # example_launch.py + # example_taking_arguments_launch.py from launch import LaunchDescription from launch.actions import DeclareLaunchArgument @@ -44,12 +80,11 @@ Each launch file performs the following actions: LogInfo(msg=[LaunchConfiguration('who'), ' is at ', LaunchConfiguration('where')]) ]) - .. group-tab:: XML .. code-block:: xml - + @@ -62,7 +97,7 @@ Each launch file performs the following actions: .. code-block:: yaml - # example_launch.yaml + # example_taking_arguments_launch.yaml launch: - arg: @@ -73,6 +108,35 @@ Each launch file performs the following actions: - log: message: "$(var who) is at $(var where)" +On execution, availability of the ``who`` argument is enforced, while for the ``where`` argument its default applies if not provided. +Then, a message is logged after both arguments are substituted against the current context (holding their values). + +This can be reproduced with a local copy of any of these launch files e.g.: + +.. code-block:: console + + $ ros2 launch example_taking_arguments_launch.xml + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [ERROR] [launch]: Caught exception in launch (see debug for traceback): Included launch description missing required argument 'who' (description: 'no description given'), given: [] + +.. code-block:: shell + + $ ros2 launch example_taking_arguments_launch.xml who:=someone + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [launch.user]: someone is at home + +.. code-block:: shell + + $ ros2 launch example_taking_arguments_launch.xml who:=someone where:="the movies" + + [INFO] [launch]: All log files can be found below ... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [launch.user]: someone is at the movies + Requiring processes ^^^^^^^^^^^^^^^^^^^ @@ -87,7 +151,7 @@ Each launch file performs the following actions: .. code-block:: python - # example_launch.py + # example_requiring_processes_launch.py from launch import LaunchDescription from launch.actions import DeclareLaunchArgument @@ -98,15 +162,15 @@ Each launch file performs the following actions: def generate_launch_description(): return LaunchDescription([ - DeclareLaunchArgument('duration', default_value='10'), + DeclareLaunchArgument('sleep_duration', default_value='10', description='Time to sleep, in seconds'), ExecuteProcess( - cmd=['sleep', LaunchConfiguration('duration')], + cmd=['sleep', LaunchConfiguration('sleep_duration')], on_exit=[ LogInfo(msg='Done sleeping!'), Shutdown(reason='main process terminated') ] ), - LogInfo(msg=['Sleeping for ', LaunchConfiguration('duration'), ' seconds']), + LogInfo(msg=['Sleeping for ', LaunchConfiguration('sleep_duration'), ' seconds']), ]) .. group-tab:: XML @@ -121,13 +185,32 @@ Each launch file performs the following actions: # Currently unsupported +On execution, availability of the ``sleep_duration`` argument is enforced, applying its default value if necessary. +Then, a ``sleep`` command is started to wait for as long as ``sleep_duration`` specifies. +Additionally, when this command exits, a shutdown is initiated. +Until then (or user interrupt), ``launch`` remain idle. + +This can be reproduced with a local copy of the Python launch file (XML and YAML launch files cannot describe this example yet) e.g.: + +.. code-block:: console + + $ ros2 launch example_requiring_processes_launch.py + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [launch.user]: Sleeping for 10 seconds + [INFO] [sleep-1]: process started with pid [NNNNNN] + [INFO] [sleep-1]: process has finished cleanly [pid NNNNNN] + [INFO] [launch.user]: Done sleeping! + [INFO] [launch]: process[sleep-1] was required: shutting down launched system + Replicating hierarchies ^^^^^^^^^^^^^^^^^^^^^^^ Each launch file performs the following actions: * Declares an argument without defaults. -* Generates namespaced groups iteratively based on that argument. +* Generates multiple namespaced groups of nodes based on that argument. .. tabs:: @@ -135,7 +218,7 @@ Each launch file performs the following actions: .. code-block:: python - # example_launch.py + # example_replicating_hierarchies_launch.py from launch import LaunchDescription from launch.actions import DeclareLaunchArgument @@ -161,7 +244,7 @@ Each launch file performs the following actions: def generate_launch_description(): return LaunchDescription([ - DeclareLaunchArgument('turtles'), + DeclareLaunchArgument('turtles', description='A space-separated list of turtle names'), OpaqueFunction( function=generate_turtles_description, args=[LaunchConfiguration('turtles')]) @@ -179,6 +262,29 @@ Each launch file performs the following actions: # Currently unsupported +On execution, availability of the ``turtles`` argument is first enforced. +Then, the ``generate_turtles_description`` Python function is invoked with both the current context and the ``turtles`` configuration variable as arguments. +This function has no side effect -- it is used as a escape hatch to dynamically extend the ``launch`` description. +To do so, it evaluates the ``turtles`` configuration variable in context and, assuming it is a spaced-separated list of turtle names, it produces a group per turtle. +Each group sets the corresponding turtle name as namespace for all ROS 2 nodes within and starts a ``turtlesim`` node. + +This can be reproduced with a local copy of the Python launch file (XML and YAML launch files cannot describe this example yet) e.g.: + +.. code-block:: console + + $ ros2 launch example_replicating_hierarchies_launch.py turtles:="alice bob" + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [turtlesim_node-1]: process started with pid [NNNNNN] + [INFO] [turtlesim_node-2]: process started with pid [NNNNNN] + [turtlesim_node-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [alice.turtlesim]: Starting turtlesim with node name /alice/turtlesim + [turtlesim_node-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [bob.turtlesim]: Starting turtlesim with node name /bob/turtlesim + [turtlesim_node-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [bob.turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000] + [turtlesim_node-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [alice.turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000] + +A pair of ``turtlesim`` windows will pop up: one for ``alice``, one for ``bob``. + Cleaning after ^^^^^^^^^^^^^^ @@ -194,7 +300,7 @@ Each launch file performs the following actions: .. code-block:: python - # example_launch.py + # example_cleaning_after_launch.py from launch import LaunchDescription from launch.actions import DeclareLaunchArgument @@ -207,7 +313,7 @@ Each launch file performs the following actions: def generate_launch_description(): return LaunchDescription([ - DeclareLaunchArgument('timeout'), + DeclareLaunchArgument('timeout', description='Timeout before shutdown, in seconds'), RegisterEventHandler(OnShutdown(on_shutdown=[ ExecuteProcess(cmd=['rm', '-f', '/tmp/resource']), ])), @@ -229,6 +335,357 @@ Each launch file performs the following actions: # Currently unsupported +On execution, availability of the ``timeout`` argument is enforced first. +Then, an ``rm -f`` command to drop a temporary ``/tmp/resource`` file is registered for execution upon shutdown. +That temporary file is created by a ``touch`` command initiated right after. +A ``launch`` shutdown is finally scheduled to occur after ``timeout`` seconds. + +This can be reproduced with a local copy of the Python launch file (XML and YAML launch files cannot describe this example yet) e.g.: + +.. code-block:: console + + $ ros2 launch example_cleaning_after_launch.py timeout:=5 + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [touch-1]: process started with pid [NNNNNN] + [INFO] [touch-1]: process has finished cleanly [pid NNNNNN] + # ...and after 5 seconds + [INFO] [rm-2]: process started with pid [NNNNNN] + [INFO] [rm-2]: process has finished cleanly [pid NNNNNN] + +Configuring nodes +^^^^^^^^^^^^^^^^^ + +Each talker launch file performs the following actions: + +* Declares an argument without defaults. +* Starts a talker node, passing command line arguments to it. + +Each example launch file performs the following actions: + +* Declares an argument with defaults. +* Forces a name remapping on all nodes. +* Includes the talker launch file with a given argument. +* Starts a listener node. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # talker_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import Shutdown + from launch.substitutions import LaunchConfiguration + from launch_ros.actions import Node + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('cycles', description='number of times to talk'), + Node( + package='demo_nodes_py', executable='talker_qos', output='screen', + arguments=['--reliable', '-n', LaunchConfiguration('cycles')]), + ]) + + .. code-block:: python + + # example_configuring_nodes_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import IncludeLaunchDescription + from launch.substitutions import LaunchConfiguration + from launch_ros.actions import Node + from launch_ros.actions import SetRemap + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('shared_topic' default_value='chat'), + SetRemap('chatter', dst=LaunchConfiguration('shared_topic')), + IncludeLaunchDescription('talker_launch.py', launch_arguments=[('cycles', '10')]), + Node(package='demo_nodes_py', executable='listener', output='screen') + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + + + + + + .. code-block:: xml + + + + + + + + + + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # talker_launch.yaml + + launch: + - arg: + name: "cycles" + description: "number of times to talk" + - node: + pkg: "demo_nodes_py" + exec: "talker_qos" + args: "--reliable -n $(var cycles)" + output: "screen" + + .. code-block:: yaml + + # example_configuring_nodes_launch.yaml + + launch: + - arg: + name: "shared_topic" + default: "chat" + - set_remap: + from: "chatter" + to: "$(var shared_topic)" + - include: + file: "talker_launch.yaml" + arg: + - name: "cycles" + value: "10" + - node: + pkg: "demo_nodes_py" + exec: "listener" + output: "screen" + +On execution, availability of the ``shared_topic`` argument is enforced, applying defaults if necessary. +Then, a remapping rule for the ``chatter`` topic is applied globally, to all nodes in the launch file. +The talker launch file is included, providing a value for the ``cycles`` argument. +This ``cycles`` argument is passed as the number of talking cycles for the talker node on start. +Finally, a listener node is started. + +This can be reproduced with a local copy of any of the launch file sets e.g.: + +.. code-block:: console + + $ ros2 launch example_configuring_nodes_launch.yaml + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [talker_qos-1]: process started with pid [NNNNNN] + [INFO] [listener-2]: process started with pid [NNNNNN] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Reliable talker + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 0" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 0] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 1" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 1] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 2" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 2] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 3" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 3] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 4" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 4] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 5" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 5] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 6" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 6] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 7" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 7] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 8" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 8] + [talker_qos-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker_qos]: Publishing: "Hello World: 9" + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 9] + [INFO] [talker_qos-1]: process has finished cleanly [pid NNNNNN] + +Switching modes +^^^^^^^^^^^^^^^ + +Each launch file performs the following actions: + +* Declares a boolean argument, false by default. +* Starts talker and a listener composable nodes if the argument is true. +* Starts talker and a listener nodes if the argument is false. + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + # example_switching modes_launch.py + + from launch import LaunchDescription + from launch.actions import DeclareLaunchArgument + from launch.actions import GroupAction + from launch.actions import IncludeLaunchDescription + from launch.actions import LogInfo + from launch.conditions import IfCondition + from launch.conditions import UnlessCondition + from launch.substitutions import LaunchConfiguration + from launch_ros.actions import Node + from launch_ros.actions import ComposableNodeContainer + from launch_ros.descriptions import ComposableNode + from launch_ros.substitutions import FindPackagePrefix + + def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument('use_composition', default_value='false'), + GroupAction( + condition=IfCondition(LaunchConfiguration('use_composition')), + actions=[ + LogInfo(msg='Running talker and listener composable nodes in the same process'), + LogInfo(msg=["Components taken from the 'composition' package (in ", FindPackagePrefix('composition'), ")"]), + ComposableNodeContainer( + package='rclcpp_components', + executable='component_container', + name='container', + namespace='/', + output='screen', + composable_node_descriptions=[ + ComposableNode(package='composition', plugin='composition::Talker', name='talker'), + ComposableNode(package='composition', plugin='composition::Listener', name='listener') + ] + ), + ] + ), + GroupAction( + condition=UnlessCondition(LaunchConfiguration('use_composition')), + actions=[ + LogInfo(msg='Running talker and listener nodes in separate processes'), + LogInfo(msg=["Nodes taken from the 'demo_nodes_cpp' package (in ", FindPackagePrefix('demo_nodes_cpp'), ")"]), + Node(package='demo_nodes_cpp', executable='talker', name='talker', output='screen'), + Node(package='demo_nodes_cpp', executable='listener', name='listener', output='screen') + ] + ) + ]) + + .. group-tab:: XML + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + .. group-tab:: YAML + + .. code-block:: yaml + + # example_switching_modes_launch.yaml + + launch: + - arg: + name: "use_composition" + default: "false" + - group: + if: "$(var use_composition)" + children: + - log: + message: "Running talker and listener composable nodes in the same process" + - log: + message: "Components taken from the 'composition' package in $(find-pkg-prefix composition)" + - node_container: + pkg: "rclcpp_components" + exec: "component_container" + name: "container" + namespace: "/" + output: "screen" + composable_node: + - pkg: "composition" + plugin: "composition::Talker" + name: "talker" + - pkg: "composition" + plugin: "composition::Listener" + name: "listener" + - group: + unless: "$(var use_composition)" + children: + - log: + message: "Running talker and listener nodes in separate processes" + - log: + message: "Nodes taken from the 'demo_nodes_cpp' package in $(find-pkg-prefix demo_nodes_cpp)" + - node: + pkg: "demo_nodes_py" + exec: "talker" + name: "talker" + output: "screen" + - node: + pkg: "demo_nodes_py" + exec: "listener" + name: "listener" + output: "screen" + +On execution, availability of the ``use_composition`` argument is enforced, applying defaults if necessary. +If ``use_composition`` is true, talker and listener composable nodes are started in a component container. +If ``use_composition`` is false, talker and listener nodes are started in their own processes. +In both cases, informational logs are produced. + +This can be reproduced with a local copy of any of the launch file sets e.g.: + +.. code-block:: console + + $ ros2 launch example_switching_modes_launch.xml + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [launch.user]: Running talker and listener nodes in separate processes + [INFO] [launch.user]: Nodes taken from the 'demo_nodes_cpp' package in /opt/ros/... + [INFO] [talker-1]: process started with pid [NNNNNN] + [INFO] [listener-2]: process started with pid [NNNNNN] + [talker-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker]: Publishing: 'Hello World: 1' + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 1] + # ... and on and on + +.. code-block:: console + + $ ros2 launch example_switching_modes_launch.xml use_composition:=true + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [launch.user]: Running talker and listener composable nodes in the same process + [INFO] [launch.user]: Components taken from the 'composition' package in /opt/ros/... + [INFO] [component_container-1]: process started with pid [NNNNNN] + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Load Library: /opt/ros/humble/lib/libtalker_component.so + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Found class: rclcpp_components::NodeFactoryTemplate + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Instantiate class: rclcpp_components::NodeFactoryTemplate + [INFO] [launch_ros.actions.load_composable_nodes]: Loaded node '/talker' in container '/container' + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Load Library: /opt/ros/humble/lib/liblistener_component.so + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Found class: rclcpp_components::NodeFactoryTemplate + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [container]: Instantiate class: rclcpp_components::NodeFactoryTemplate + [INFO] [launch_ros.actions.load_composable_nodes]: Loaded node '/listener' in container '/container' + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker]: Publishing: 'Hello World: 1' + [component_container-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 1] + # ... and on and on + Managing lifecycles ^^^^^^^^^^^^^^^^^^^ @@ -237,13 +694,16 @@ Each launch file performs the following actions: * Registers a custom event handler to manage lifecycles synchronously. * Starts talker and listener managed nodes. +Note this is a significantly more complex example, effectively extending ``launch`` to achieve its goal. +For further reference on managed nodes, see :doc:`../Tutorials/Demos/Managed-Nodes`. + .. tabs:: .. group-tab:: Python .. code-block:: python - # example_launch.py + # example_managing_lifecycles_launch.py from launch import LaunchDescription from launch.actions import RegisterEventHandler @@ -265,32 +725,30 @@ Each launch file performs the following actions: lifecycle_msgs.msg.State.PRIMARY_STATE_INACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE } - SHUTDOWN_SEQUENCE = { - lifecycle_msgs.msg.State.PRIMARY_STATE_UNCONFIGURED: lifecycle_msgs.msg.Transition.TRANSITION_UNCONFIGURED_SHUTDOWN, - lifecycle_msgs.msg.State.PRIMARY_STATE_INACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_INACTIVE_SHUTDOWN, - lifecycle_msgs.msg.State.PRIMARY_STATE_ACTIVE: lifecycle_msgs.msg.Transition.TRANSITION_ACTIVE_SHUTDOWN - } - def __init__(self, nodes): + # Track the state of all managed nodes in a dictionary. self.managees = {node: lifecycle_msgs.msg.State.PRIMARY_STATE_UNKNOWN for node in nodes} + # Configure event matcher to handle both process start and state transition events from all managed nodes. matcher = (lambda event: ( isinstance(event, (ProcessStarted, StateTransition)) and event.action in self.managees)) super().__init__(matcher=matcher) def handle(self, event, context): - if isinstance(event, ProcessStarted): + if isinstance(event, ProcessStarted): # node just started, unconfigured self.managees[event.action] = lifecycle_msgs.msg.State.PRIMARY_STATE_UNCONFIGURED - if isinstance(event, StateTransition): + if isinstance(event, StateTransition): # node changed state, track it self.managees[event.action] = event.msg.goal_state.id states = list(set(self.managees.values())) common_state = states[0] if len(states) == 1 else None if common_state in self.BRINGUP_SEQUENCE: + # all managed nodes have reached the same state and the bringup sequence is not complete matcher = lambda action: action in self.managees + # trigger state transitions for all managed nodes to next state in the bringup sequence return [ EmitEvent(event=ChangeState( lifecycle_node_matcher=matcher, transition_id=self.BRINGUP_SEQUENCE[common_state]))] - return None + return None # do nothing def generate_launch_description(): first_talker_node = LifecycleNode( @@ -322,6 +780,36 @@ Each launch file performs the following actions: # Currently unsupported +During configuration, a ``LifecycleManager`` event handler is instantiated, taking references to both talker nodes. +On execution, the ``LifecycleManager`` event handler is registered and all nodes are started. +The ``LifecycleManager`` event handler then takes care of managing both talker nodes from the unconfigured state +through the configured state and to the active state. + +This can be reproduced with a local copy of the Python launch file (``launch`` cannot be extended like this in XML and YAML launch files) e.g.: + +.. code-block:: console + + $ ros2 launch example_managing_lifecycles_launch.py + + [INFO] [launch]: All log files can be found below /... + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [lifecycle_talker-1]: process started with pid [NNNNNN] + [INFO] [lifecycle_talker-2]: process started with pid [NNNNNN] + [INFO] [lifecycle_listener-3]: process started with pid [NNNNNN] + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: notify callback: Transition from state unconfigured to configuring + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: notify callback: Transition from state configuring to inactive + [lifecycle_talker-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [first_talker]: on_configure() is called. + [lifecycle_talker-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [second_talker]: on_configure() is called. + [lifecycle_talker-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [second_talker]: on_activate() is called. + [lifecycle_talker-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [first_talker]: on_activate() is called. + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: notify callback: Transition from state inactive to activating + [lifecycle_talker-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [second_talker]: Lifecycle publisher is active. Publishing: [Lifecycle HelloWorld #1] + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: data_callback: Lifecycle HelloWorld #1 + [lifecycle_talker-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [first_talker]: Lifecycle publisher is active. Publishing: [Lifecycle HelloWorld #1] + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: notify callback: Transition from state activating to active + [lifecycle_listener-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: data_callback: Lifecycle HelloWorld #1 + # ... and on and on until user interruption + Launching many nodes ^^^^^^^^^^^^^^^^^^^^ @@ -340,7 +828,7 @@ Each launch file performs the following actions: .. code-block:: python - # example_launch.py + # example_many_nodes_launch.py import os @@ -484,7 +972,7 @@ Each launch file performs the following actions: .. code-block:: xml - + @@ -537,7 +1025,7 @@ Each launch file performs the following actions: .. code-block:: yaml - # example_launch.yaml + # example_many_nodes_launch.yaml launch: @@ -624,50 +1112,47 @@ Each launch file performs the following actions: from: "/output/cmd_vel" to: "/turtlesim2/turtle1/cmd_vel" -Using launch files from the command line --------------------------------------------- - -Launching -^^^^^^^^^ - -Any of the launch files above can be run with ``ros2 launch``. -To try them locally, you can either create a new package and use - -.. code-block:: console - - ros2 launch - -or run the file directly by specifying the path to the launch file - -.. code-block:: console - - ros2 launch - -Setting arguments -^^^^^^^^^^^^^^^^^ - -To set the arguments that are passed to the launch file, you should use ``key:=value`` syntax. -For example, you can set the value of ``background_r`` in the following way: - -.. code-block:: console - - ros2 launch background_r:=255 +On execution, availability of background color and chatter namespace arguments is enforced, applying defaults if necessary. +Then, talker-listener pairs, as described in a demo launch file, are launched with different namespaces. +Finally, multiple ``turtlesim`` nodes are started, using default parameters, setting custom background colors, and remapping topics. -or +This can be reproduced with a local copy of any of the launch files e.g.: .. code-block:: console - ros2 launch background_r:=255 - -Controlling the turtles -^^^^^^^^^^^^^^^^^^^^^^^ + $ ros2 launch example_many_nodes_launch.py + + [INFO] [launch]: All log files can be found below /home/hidmic/.ros/log/2023-09-19-20-16-48-522323-mhidalgo-spot-197115 + [INFO] [launch]: Default logging verbosity is set to INFO + [INFO] [talker-1]: process started with pid [NNNNNN] + [INFO] [listener-2]: process started with pid [NNNNNN] + [INFO] [talker-3]: process started with pid [NNNNNN] + [INFO] [listener-4]: process started with pid [NNNNNN] + [INFO] [talker-5]: process started with pid [NNNNNN] + [INFO] [listener-6]: process started with pid [NNNNNN] + [INFO] [talker-7]: process started with pid [NNNNNN] + [INFO] [listener-8]: process started with pid [NNNNNN] + [INFO] [turtlesim_node-9]: process started with pid [NNNNNN] + [INFO] [turtlesim_node-10]: process started with pid [NNNNNN] + [INFO] [mimic-11]: process started with pid [NNNNNN] + [turtlesim_node-10] [INFO] [TTTTTTTTTT.TTTTTTTTT] [turtlesim2.sim]: Starting turtlesim with node name /turtlesim2/sim + [turtlesim_node-9] [INFO] [TTTTTTTTTT.TTTTTTTTT] [turtlesim1.sim]: Starting turtlesim with node name /turtlesim1/sim + [turtlesim_node-10] [INFO] [TTTTTTTTTT.TTTTTTTTT] [turtlesim2.sim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000] + [turtlesim_node-9] [INFO] [TTTTTTTTTT.TTTTTTTTT] [turtlesim1.sim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000] + [talker-1] [INFO] [TTTTTTTTTT.TTTTTTTTT] [talker]: Publishing: 'Hello World: 1' + [listener-2] [INFO] [TTTTTTTTTT.TTTTTTTTT] [listener]: I heard: [Hello World: 1] + [talker-3] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.py.ns.talker]: Publishing: 'Hello World: 1' + [listener-4] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.py.ns.listener]: I heard: [Hello World: 1] + [talker-5] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.xml.ns.talker]: Publishing: 'Hello World: 1' + [listener-6] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.xml.ns.listener]: I heard: [Hello World: 1] + [talker-7] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.yaml.ns.talker]: Publishing: 'Hello World: 1' + [listener-8] [INFO] [TTTTTTTTTT.TTTTTTTTT] [chatter.yaml.ns.listener]: I heard: [Hello World: 1] To test that the remapping is working, you can control the turtles by running the following command in another terminal: .. code-block:: console - ros2 run turtlesim turtle_teleop_key --ros-args --remap __ns:=/turtlesim1 - + ros2 run turtlesim turtle_teleop_key --ros-args --remap __ns:=/turtlesim1 Python, XML, or YAML: Which should I use? ----------------------------------------- From 1ecbbac0e4a1339cc85886fb9a60f50c9ede4214 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Fri, 22 Sep 2023 10:03:47 -0300 Subject: [PATCH 4/5] Refine launch concepts page Signed-off-by: Michel Hidalgo --- source/Concepts/Basic/About-Launch.rst | 40 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/source/Concepts/Basic/About-Launch.rst b/source/Concepts/Basic/About-Launch.rst index 4c529915c2a..aa43dd4aa23 100644 --- a/source/Concepts/Basic/About-Launch.rst +++ b/source/Concepts/Basic/About-Launch.rst @@ -18,18 +18,38 @@ These descriptions are then executed, starting processes, monitoring them, repor Moving parts ------------ -``launch`` works with _actions_, abstract representations of computational procedures with side effects on its execution environment. Logging to standard output, executing a program, shutting down ``launch`` itself, are examples of actions. An action may also encompass another action or a collection thereof to describe more complex functionality. A collection of actions makes up the _description_ of a procedure that ``launch`` can take and execute. These descriptions are typically read from source files, so called _launch files_, which may be written in Python, or using specific XML or YAML syntax. +``launch`` works with [_actions_](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Action), abstract representations of computational procedures with side effects on its execution environment. +Logging to standard output, executing a program, forcing a ``launch`` shutdown, are examples of actions. +An action may also encompass another action or a collection thereof to describe more complex functionality. +A collection of actions makes up the [_description_](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.LaunchDescription) of a procedure that ``launch`` can take and execute. +These descriptions are typically read from [sources](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.LaunchDescriptionSource), so called _launch files_, which may be written in Python, or using specific XML or YAML syntax. -While the extent of the execution environment of an action depends on its nature e.g. logging to standard output is circumscribed to the ``launch`` process whereas executing a program reaches out to the host operating system, ``launch`` maintains an execution _context_ in which these actions take place and through which these can interact between them and with the user. -The ``launch`` context holds _configuration variables_ (i.e. state) and propagates _events_, both of which are available to actions and to ``launch`` itself. -Events are signals that originate internally in actions or externally on user input. It is through events, for example, that ``launch`` description execution (i.e. inclusion) is triggered. -Configuration variables populate the ``launch`` context. It is through configuration variables, for example, that ``launch`` descriptions can take arguments before execution. +While the extent of the execution environment of an action depends on its nature e.g. logging to standard output is circumscribed to the ``launch`` process whereas executing a program reaches out to the host operating system, ``launch`` maintains an execution [_context_](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.LaunchContext) in which these actions take place and through which these can interact between them and with the user. +The ``launch`` context holds _configuration variables_ and propagates _events_, both of which are available to actions and to ``launch`` itself: -In connection with its context, ``launch`` defines _conditions_, _substitutions_, and _event handlers_ to broaden its scope of application. -The first two are different forms of expressions evaluated in ``launch`` context (though they may also tap into the larger execution environment): conditions are boolean predicates, used to define conditional actions, while substitutions are string interpolation expressions that enable dynamic ``launch`` descriptions. -Event handlers, on the other hand, are collections of actions registered for execution when and if the corresponding event occurs. +* Configuration variables populate the ``launch`` context as shared state. + It is as configuration variables, for example, that arguments to ``launch`` descriptions are made available. + Configuration variables are organized in scopes with visibility and persistence implications. + These scopes are, however, not implicitly managed by the ``launch`` context but explicitly defined through [pushing](https://docs.ros.org/en/rolling/p/launch/launch.actions.html#launch.actions.PushLaunchConfigurations) and [popping](https://docs.ros.org/en/rolling/p/launch/launch.actions.html#launch.actions.PopLaunchConfigurations) actions. + In general and by default, all configuration variables live in the same scope. -As it may be apparent by now, ``launch`` descriptions are essentially programs in a domain specific language tailored for process orchestration, and, in particular, ROS 2 system orchestration. When composed using the building blocks available in its native Python implementation, these descriptions resemble `ASTs `_ in procedural programming languages. The analogy has its limits: context is not implicitly restricted to syntactical boundaries like it would for typical variable scopes, and action execution is naturally concurrent as opposed to sequential, to name a few. However, it does bring about an important distinction that is easy to miss when writing launch files in Python: no action nor condition nor substitution carries out a computation upon instantiation but simply specifies a computation to be carried out in runtime. +* [Events](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Event) are signals emitted and reacted on by actions or ``launch`` itself. + An action completing, a process exiting, ``launch`` shutting down, are examples of events. + These signals have no inherent side effects, only those that result from actions handling them (if any). + Events only exist within the ``launch`` context, but are not bound to any scopes. + +Actions and events are the main moving parts in ``launch``, even if events are used indirectly more often than not (i.e. it is through events that the ``launch`` internal event loop is driven). +In addition to these, and to better leverage configuration variables, ``launch`` defines _conditions_, _substitutions_, and _event handlers_: + +* [Conditions](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Condition) encapsulate boolean predicates evaluated in ``launch`` context, and therefore in runtime. + These are mainly used to define actions that execute _if__ or _unless_ a given boolean predicate turns out to be true. +* [Substitutions](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Substitution) are string interpolation expressions evaluated in ``launch`` context, though these may also tap into the larger execution environment. + Evaluating a configuration variable value, fetching an environment variable value, retrieve the absolute path in the filesystem of an executable file, are examples of substitutions. + Substitutions are the closest to general purpose expressions in ``launch``, enabling dynamic ``launch`` descriptions. +* [Event handlers](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.EventHandler) are similar to actions, but their execution is bound to the occurrence of an specific set of events. + These are typically defined in terms of a collection of actions to execute when and if a matching event occurs. + +In a way, ``launch`` descriptions are essentially programs in a domain specific language tailored for process orchestration, and, in particular, ROS 2 system orchestration. When composed using the building blocks available in its native Python implementation, these descriptions resemble `ASTs `_ in procedural programming languages. The analogy has its limits: context is not implicitly restricted to syntactical boundaries like it would for typical variable scopes, and action execution is naturally concurrent as opposed to sequential, to name a few. However, it does bring about an important distinction that is easy to miss when writing launch files in Python: no action nor condition nor substitution carries out a computation upon instantiation but simply specifies a computation to be carried out in runtime. .. note:: @@ -50,5 +70,5 @@ References ---------- The most thorough reference on the design of ``launch`` is, unsurprisingly, its seminal `design document `__ (which even includes functionality not yet available). -[``launch`` documentation](https://docs.ros.org/en/rolling/p/launch`` complements it, detailing the architecture of the core Python library. +[``launch`` documentation](https://docs.ros.org/en/rolling/p/launch) complements it, detailing the architecture of the core Python library. For everything else, both ``launch`` and ``launch_ros`` APIs are documented. From 5aab2c72decf679d577658e44d11a037406ff45b Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 28 Sep 2023 16:52:19 -0300 Subject: [PATCH 5/5] Apply minor grammar fixes Signed-off-by: Michel Hidalgo --- source/Concepts/Basic/About-Launch.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Concepts/Basic/About-Launch.rst b/source/Concepts/Basic/About-Launch.rst index aa43dd4aa23..bdc7be843bd 100644 --- a/source/Concepts/Basic/About-Launch.rst +++ b/source/Concepts/Basic/About-Launch.rst @@ -42,14 +42,14 @@ Actions and events are the main moving parts in ``launch``, even if events are u In addition to these, and to better leverage configuration variables, ``launch`` defines _conditions_, _substitutions_, and _event handlers_: * [Conditions](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Condition) encapsulate boolean predicates evaluated in ``launch`` context, and therefore in runtime. - These are mainly used to define actions that execute _if__ or _unless_ a given boolean predicate turns out to be true. + These are mainly used to define actions that execute _if_ or _unless_ a given boolean predicate turns out to be true. * [Substitutions](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.Substitution) are string interpolation expressions evaluated in ``launch`` context, though these may also tap into the larger execution environment. - Evaluating a configuration variable value, fetching an environment variable value, retrieve the absolute path in the filesystem of an executable file, are examples of substitutions. + Evaluating a configuration variable value, fetching an environment variable value, retrieving the absolute path in the filesystem of an executable file, are examples of substitutions. Substitutions are the closest to general purpose expressions in ``launch``, enabling dynamic ``launch`` descriptions. * [Event handlers](https://docs.ros.org/en/rolling/p/launch/launch.html#launch.EventHandler) are similar to actions, but their execution is bound to the occurrence of an specific set of events. These are typically defined in terms of a collection of actions to execute when and if a matching event occurs. -In a way, ``launch`` descriptions are essentially programs in a domain specific language tailored for process orchestration, and, in particular, ROS 2 system orchestration. When composed using the building blocks available in its native Python implementation, these descriptions resemble `ASTs `_ in procedural programming languages. The analogy has its limits: context is not implicitly restricted to syntactical boundaries like it would for typical variable scopes, and action execution is naturally concurrent as opposed to sequential, to name a few. However, it does bring about an important distinction that is easy to miss when writing launch files in Python: no action nor condition nor substitution carries out a computation upon instantiation but simply specifies a computation to be carried out in runtime. +In a way, ``launch`` descriptions are analogous to programs in a domain specific language tailored for process orchestration, and, in particular, ROS 2 system orchestration. When composed using the building blocks available in its native Python implementation, these descriptions resemble `ASTs `_ in procedural programming languages. The analogy has its limits, however: context is not implicitly restricted to syntactical boundaries like it would for typical variable scopes, and action execution is naturally concurrent as opposed to sequential, to name a few. However, it does bring about an important distinction that is easy to miss when writing launch files in Python: no action nor condition nor substitution carries out a computation upon instantiation but simply specifies a computation to be carried out in runtime. .. note::