🤖
Introduction to ROS2 and Robotics
  • Introduction
  • ROS2
    • Index
    • IDE and CMake Setup
      • How to add additional include search path
    • ROS2 Building Blocks
      • ROS Workspace and Package Layout
      • Launch File
      • tf2
      • Quality of Service
      • Configurations
        • Rviz Configuration
      • Built-in Types
        • Built-in Message Type
    • ROS Architecture
      • Intra-process Communication
    • Navigation and Planning
      • Navigation Stack and Concepts
      • Navigation2 Implementation Overview
        • 🏗️Cost Map
        • Obstacle Avoidance and DWB Controller
      • DWB Controller
      • Page 5
    • How to launch the Nav2 stack
    • ROS2 Control
      • Online Resources
      • Overview of Codebase
    • 🍳Cookbook
      • Useful Commands
      • How to specify parameters
      • How to build the workspace
      • 🏗️How to publish message to a topic from command line?
      • How to inspect service and make a service call
      • How to properly terminate ROS and Gazebo
      • How to add and remove models in Gazebo simulation dynamically
      • 🚧How to spin nodes
    • 🛒Tutorials
      • Services and Communication between ROS2 and Gazebo
      • Subscription and Message Filters Demo
      • Executor and Spin Explained
      • Lifecycle Node Demo
      • Robotic Arm Demo
      • ⚒️Multiple Robotic Arms Simulation Demo
      • 🚧Introduction to xacro
    • Page
    • 🍺Tech Blog
      • Difference between URDF and SDF and how to convert
  • Gazebo
    • Index
    • Terminology
    • GUI
    • World Frame and Axis
    • Cookbook
    • Page 1
  • Programming in Robotics
    • C++
      • CMake
    • Python
    • Rust
  • Mathematics in Robotics
    • Linear Algebra
    • Matrix Properties
    • Probability
      • Expectation-Maximization Algorithm
    • Multivariable Function and Derivatives
  • Physics in Robotics
  • Control of Dynamic Systems
    • Dynamic Response and Transfer Function
    • Block Diagram
    • PID Controller
  • Robot Modeling and Control
    • Rotation and Homogeneous Transformation
  • Probabilistic Robotics
    • Bayes Filter
    • Kalman Filter
    • Particle Filter
    • Discrete Bayes Filter
    • Motion Model
    • Perception Model
    • Localization
    • SLAM
  • Miscellany
  • Concept Index
    • Quaternions
Powered by GitBook
On this page
  • Related Readings
  • Introduction
  • State and State Transition
  • Where are the states defined?
  • How to transit out of the finalized state?
  • Demo Code Setup
  • Transit from Unconfigured to Inactive
  • Transit from Inactive to Active
  • Download the code
  1. ROS2
  2. Tutorials

Lifecycle Node Demo

PreviousExecutor and Spin ExplainedNextRobotic Arm Demo

Last updated 1 year ago

This tutorial is based on ROS2 Humbler. The behavior may vary in a different distro.

Related Readings

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 , a lifecycle node has the following states and state transitions:

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?

How to transit out of the finalized state?

This section is personal interpretation.

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:

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.

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:

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

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();

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:

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

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

©2023 - 2024 all rights reserved

The 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 message of the . 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.

The design doc 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:

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 within the package reveals no reference to the destroy method. This raises the question: what exactly is happening in this scenario?

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 ).

The code is available for download at this .

🛒
state class
state
rcl_interfaces/lifecycle_msgs package
ROS2 Design Doc - Managed Node (Lifecycle Node)
lifecycle_node.hpp
rclcpp_lifecycle
c++ documentation
link
ROS2 Design Doc - Managed Node (Lifecycle Node)
ROS2 Lifecycle Node Officiel Demo Repo
Lifecycle Message Definition
Lifecycle Node State Definition
ROS2 Design Doc - Managed Node (Lifecycle Node)