# Lifecycle Node Demo

{% hint style="warning" %}
This tutorial is based on ROS2 Humbler. The behavior may vary in a different distro.
{% endhint %}

## Related Readings

* [ROS2 Design Doc - Managed Node (Lifecycle Node)](https://design.ros2.org/articles/node_lifecycle.html)
* [ROS2 Lifecycle Node Officiel Demo Repo](https://github.com/ros2/demos/tree/humble/lifecycle)
* [Lifecycle Message Definition](https://github.com/ros2/rcl_interfaces/tree/humble/lifecycle_msgs)
* [Lifecycle Node State Definition](https://github.com/ros2/rcl_interfaces/blob/humble/lifecycle_msgs/msg/State.msg)

## Introduction

In this tutorial, we will demonstrate how to trigger a state transition in a lifecycle node. We'll start with a brief introduction to the lifecycle node's states and transitions. Next, we'll clarify a few potentially confusing concepts. Lastly, we'll guide you through a practical example and demonstrate the state transition via the command line and the code.

## State and State Transition

According to [ROS2 Design Doc - Managed Node (Lifecycle Node)](https://design.ros2.org/articles/node_lifecycle.html), a lifecycle node has the following states and state transitions:

<figure><img src="/files/gcKMeMnD3e6pdzb4YqqH" alt=""><figcaption></figcaption></figure>

Lifecycle nodes in ROS2 have two state types: primary states and transition states. Transitioning between primary states requires invoking a specific function. For instance, to move a node from inactive to active, we use the `activate` function. This transition can be initiated either from the command line or directly in the code. During the execution of the transition function, the node enters the corresponding transition states. For example, if the `activate` function takes 10 seconds to run and we query the node's state during this window, it will return `activating`.

Before moving on to our demo, it's important to clarify a few key points.

## Where are the states defined?

The [state class](https://github.com/ros2/rclcpp/blob/humble/rclcpp_lifecycle/include/rclcpp_lifecycle/state.hpp) is implemented in the rclcpp/rclcpp\_lifecycle package. However, if you check the code, the `State` class is simply a wrapper of the id and label pair. The class does not contain any clue about the mapping between the id and the actual state name described in the design document. The mapping is defined in the [state](https://github.com/ros2/rcl_interfaces/blob/humble/lifecycle_msgs/msg/State.msg) message of the [rcl\_interfaces/lifecycle\_msgs package](https://github.com/ros2/rcl_interfaces/tree/humble/lifecycle_msgs). Therefore, the most robust way to check the current state of the lifecycle node in the code is to include `lifecycle_msgs/msg/state.hpp` and examine the `id` field of the state object.

## How to transit out of the finalized state?

{% hint style="warning" %}
This section is personal interpretation.
{% endhint %}

The design doc [ROS2 Design Doc - Managed Node (Lifecycle Node)](https://design.ros2.org/articles/node_lifecycle.html) seems to suggest that transitioning out of the `finalized` state is possible by invoking the `destroy` method. Yet, when we set the node to the `finalized` state and attempt to invoke the `destroy` method, the following results are observed:

```
learnros2@7D41:~/ros2-lifecycle-node-demo$ ros2 lifecycle get /my_demo_node
finalized [4]
:learnros2@7D41~/ros2-lifecycle-node-demo$ ros2 lifecycle set /my_demo_node destroy
Unknown transition requested, available ones are:
```

When the node is in the `finalized` state, it appears that there are no valid transitions available. Indeed, examining the implementation of transition methods in [lifecycle\_node.hpp](https://github.com/ros2/rclcpp/blob/humble/rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp#L844-L850) within the [rclcpp\_lifecycle](https://github.com/ros2/rclcpp/tree/humble/rclcpp_lifecycle) package reveals no reference to the `destroy` method. This raises the question: what exactly is happening in this scenario?

There are two plausible explanations. Firstly, like the `start` state, `destroyed` is a concept state.  This means although we understand the node is destroyed, querying it for its current state is not feasible as the node no longer exists. This explains why the `destroyed` state is not a primary state.

Secondly, the destruction of a node involves memory deallocation by an owner. However, not all nodes are under the supervision of a node manager or similar system, making it challenging to determine the recipient of the destroy command in advance.

## Demo Code Setup

The demo code will illustrate the transition from the `unconfigured` state to the `finalized` state.  We will manually trigger most transitions via the command line, except for the transition from inactive to active, which happens automatically after a pre-defined delay. Components such as timer, subscriber, and publisher are created in the `configuring` state and become functional in the `inactive` state. The node will then automatically move to the `active` state, where it can receive messages and become ready for operation.

The `ready` state is a user-define concept in our demo code. As suggested in the design document, complex software-level initialization logic can be implemented as a user-defined state machine that runs when the node is in the `active` state. In our demonstration, the user-defined state machine is trivial: the node transitions from active to ready upon receiving a message from the `ready_signal` topic.

<figure><img src="/files/QXIBVaB4iOnbsuBGu5F9" alt=""><figcaption></figcaption></figure>

## Transit from Unconfigured to Inactive

Let's first launch the system:

```
ros2 launch bringup demo.launch.py
```

It gives the following results:

```
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [demo-1]: process started with pid [280004]
```

There aren't many outputs at this step. We can check the state of the node and it should be in the `unconfigured` state:

```
learnros2@7D41:~/ros2-lifecycle-node-demo$ ros2 lifecycle get /my_demo_node
unconfigured [1]
```

To transit from the `unconfigured` state to the `inactive` state, we can invoke the `configure` method using the following command:

```
ros2 lifecycle set /my_demo_node configure
```

We will see the following outputs in the terminal where we launched the system:

```
[demo-1] [INFO] [1704042797.997082968] [my_demo_node]: on_configure() is called. Previous state is unconfigured. Internal state: is_ready=false
[demo-1] [INFO] [1704042797.997865606] [my_demo_node]: on_configure is complete. Thread ID is 140308162622336
[demo-1] [INFO] [1704042797.997879296] [my_demo_node]: thread ID in lambda: 140308090152512
[demo-1] [INFO] [1704042798.997785585] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704042799.997786979] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704042800.997802945] [my_demo_node]: Demo node is not active yet and cannot publish messages.
```

We can see the `on_configured` method is called and the node executes the `publish` method at a fixed rate. This shows that the timer is activated when the node is in the inactive state. Check the node state again by running the command below

```
ros2 lifecycle get /my_demo_node
```

It confirms that the node is in the `inactive` state.

```
inactive [2]
```

If we publish a message to the `read_signal` topic, we can see that the node is able to subscirbe to the topic when it's in the `inactive` state. To publish a message, use the following command:

{% code overflow="wrap" %}

```
ros2 topic pub --once /ready_signal std_msgs/msg/String "{data: 'Hello from terminal'}"
```

{% endcode %}

The output confirms the node can receive the message:

```
[demo-1] [INFO] [1704043103.047689958] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704043104.047725655] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704043104.744767441] [my_demo_node]: Demo node is ready for work now.
[demo-1] [INFO] [1704043105.047731930] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704043106.047750263] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704043107.047752290] [my_demo_node]: Demo node is not active yet and cannot publish messages.
```

## Transit from Inactive to Active

We add a small code in the `on_configure` method to make the transit from inactive to active automatic:

```
std::thread async_task(
        [this]()
        {
            std::ostringstream ss;
            ss << std::this_thread::get_id();
            RCLCPP_INFO(this->get_logger(), "thread ID in lambda: %s", ss.str().c_str());
            std::this_thread::sleep_for(std::chrono::seconds(20));
            RCLCPP_INFO(this->get_logger(), "Activate the node by itself");
            this->activate();
            RCLCPP_INFO(this->get_logger(), "[lambda] Activation is completed.");
        }
);
async_task.detach();
```

Note a few details. We use `sleep_for` to delay the invocation of the `activate` function and this code runs in a separate thread. The `detach` is required because we want to separate the execution from the thread object; otherwise, when the execution exits the `on_configure` method, the thread object will terminate the program (see [c++ documentation](https://en.cppreference.com/w/cpp/thread/thread/%7Ethread)).

Once the node reaches the `inactive` state, no further commands are required. Depending on the delay specified in the code, simply wait a few seconds and you should observe the following outputs:

```
[demo-1] [INFO] [1704044107.059493913] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704044108.059540732] [my_demo_node]: Demo node is not active yet and cannot publish messages.
[demo-1] [INFO] [1704044108.059703175] [my_demo_node]: Activate the node by itself
[demo-1] [INFO] [1704044108.059876436] [my_demo_node]: on_activate() is called. Previous state is inactive. Internal state: is_ready=false
[demo-1] [INFO] [1704044108.059975961] [my_demo_node]: [lambda] Activation is completed.
[demo-1] [INFO] [1704044109.059562172] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044110.059572780] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044111.059531749] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044112.059586519] [my_demo_node]: Publish message: internal_state: is_ready=false
```

If you haven't published a message to the `ready_signal` topic, the ready flag of the node remains false. To set the node to ready, you can manually publish a message from the command line:

{% code overflow="wrap" %}

```
ros2 topic pub --once /ready_signal std_msgs/msg/String "{data: 'Hello from terminal'}"
```

{% endcode %}

And you should see the following messages:

```
[demo-1] [INFO] [1704044121.059626693] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044122.059679773] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044123.059692743] [my_demo_node]: Publish message: internal_state: is_ready=false
[demo-1] [INFO] [1704044123.265610986] [my_demo_node]: Demo node is ready for work now.
[demo-1] [INFO] [1704044124.059697042] [my_demo_node]: Publish message: internal_state: is_ready=true
[demo-1] [INFO] [1704044125.059758017] [my_demo_node]: Publish message: internal_state: is_ready=true
[demo-1] [INFO] [1704044126.059715186] [my_demo_node]: Publish message: internal_state: is_ready=true
```

## Download the code

The code is available for download at this [link](https://learnros2.gumroad.com/l/qfxlg).

©2023 - 2024 all rights reserved


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.learnros2.com/ros/tutorials/lifecycle-node-demo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
