Skip to content

Open-Edge-Robotics/A.RobotAI-LGE-ROS2-LifecycleManager

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 

Repository files navigation

A.RobotAI-LGE-ROS2-LifecycleManager

A C++ lifecycle orchestration layer for ROS 2 on resource-constrained embedded systems.

🚀 Key Results

⚡ 81.3% faster boot time on real robot hardware
💾 38.7% lower RAM usage vs ros2 launch (Stable)
🧠 Deterministic ROS 2 lifecycle orchestration in C++

Validated on a commercial robot platform (IFA 2025 showcase).

Performance Summary

  • Boot time: 60.0s → 11.2s (-81.3%)
  • RAM usage (Stable): 442MB → 271MB (~38.7% reduction)
  • Avg RAM during boot: 229MB → 201MB

📌 Tested on low-end embedded SoCs (e.g., 1GB RAM, Cortex-A35/A53)

📌 Boot time is defined as the time from process start until all required nodes reach the ACTIVE lifecycle state.

📌 Measurements are based on lifecycle activation logs and OS-level memory metrics collected under identical workloads.

👉 These results suggest a practical deployment path for ROS 2 on highly constrained embedded hardware.

🎯 Why this matters

ROS 2 can remain challenging to deploy on low-cost embedded robots when memory budgets and startup constraints are tight.

This project demonstrates that ROS 2 can be:

  • deployed on 1GB-class hardware
  • booted with deterministic lifecycle coordination
  • used in real production environments

⚖️ Comparison

Approach Strengths Limitations
ros2 launch (Python) Flexible, easy to use High RAM usage, non-deterministic startup
systemd Stable process management No ROS 2 lifecycle awareness
ROS 2 Composition Efficient intra-process execution No system-level orchestration
micro-ROS Optimized for MCUs Not for Linux-based systems
LifecycleManager Deterministic, low-overhead, lifecycle-aware More integration effort than default launch-based workflows

🚧 [NOTICE] Project Status

This repository currently provides architecture documentation, YAML configuration examples, measurement methodology, and empirical validation results for the Deterministic Lifecycle Manager.

These materials are intended to make the design and observed system behavior technically reviewable before the full implementation is publicly released.

A partial or full source release under Apache License 2.0 is being prepared through internal compliance review.

📑 Table of Contents

  1. Overview - "What & Why?"
  2. Structural Pain Points in Production Systems - "Pain Point"
  3. Architecture & LifecycleManager - "Solution"
  4. Deterministic Boot Flow - "Deep-dive"
  5. Metrics & Validation - "Validation"
  6. Source, Build & License - "Open-Source Status"

1. Overview - "What & Why?"

This document introduces the "Deterministic Lifecycle Manager", a C++‑based lifecycle orchestration service designed to run ROS 2 reliably on low‑end embedded robotic platforms. The target systems are cost-constrained commercial robotic platforms built on entry-level SoCs such as Rockchip PX30, LG DQ1, or other Cortex-A35-class architectures, typically equipped with less than 1GB of RAM. On these platforms, boot-time behavior and peak resource usage critically impact system stability.

🔴 Motivation

During system integration and production‑equivalent platform evaluation, we repeatedly encountered critical issues when using the standard Python‑based ros2 launch workflow on resource‑constrained hardware. The most common problems were:

  • High baseline RAM usage before application logic starts
  • Frequent OOM (Out Of Memory) kills during early boot
  • Unstable and non-deterministic startup sequences in production images Similar issues were repeatedly observed during evaluation on multiple low-end platforms, with the detailed quantitative results in this repository collected on the LG DQ1-based test platform. In our evaluated configuration, these issues were not sufficiently resolved through parameter tuning, launch configuration changes, or partial optimization. Our working conclusion was that orchestration overhead in the evaluated Python-based launch path was a major contributor under tight memory constraints.

🟢 Design Approach

To address these limitations, we implemented the Deterministic Lifecycle Manager as a minimal, native C++ service that launches and supervises ROS 2 nodes directly as OS-level processes. Key characteristics include:

  • No Python-based launch dependency on the target system
  • ROS 2 nodes built and executed as native C++ binaries
  • Hardware-Aware Concurrent Spawning: Utilizes C++ threads alongside standard POSIX primitives (fork(), execvp(), SIGCHLD) to balance parallel execution. By throttling simultaneous process launches based on system hardware concurrency (CPU cores), it mitigates excessive CPU contention and OS scheduler overhead to ensure system stability.
  • Dual-Launch "Benchmark Mode": Supports toggling between native C++ spawning and legacy script-based execution (std::system()) to allow direct A/B performance comparisons (e.g., Python vs. C++ native). This design preserves standard ROS 2 lifecycle semantics and DDS-based communication while reducing the runtime overhead associated with the evaluated Python-based launch path.

💡 Note: Deterministic Lifecycle Manager is not a replacement for ROS 2. It is a focused orchestration layer that provides deterministic and resource-efficient boot and lifecycle management, specifically tailored for low-end embedded systems used in cost-constrained production robots.

2. Structural Pain Points in Production Systems - "Pain Point"

Structural Pain Points in Production Systems

This section explains why the standard Python‑based ros2 launch workflow becomes a reliability bottleneck on low‑end SoCs, based on issues repeatedly observed during production‑equivalent system integration.

2.1 Excessive Runtime Overhead During Boot

ros2 launch relies on Python processes that are loaded and initialized during system boot.

  • On low-cost hardware with 1 GB or less of RAM, this introduces substantial overhead before any application logic starts.
  • As the number of nodes increases, Python interpreter initialization and runtime management consume a significant portion of system resources, frequently leading to memory pressure and OOM (Out Of Memory) events during early boot.

2.2 Non‑Deterministic Startup

  • Although ros2 launch supports concurrent spawning, it does not enforce strict OS‑level startup ordering or readiness guarantees.
  • As a result, dependent nodes may start before prerequisite nodes are fully initialized, leading to race conditions and unstable boot behavior in production environments.

2.3 Fragmented Lifecycle Control After Launch

The launch system focuses on process creation and parameter loading.

  • After startup, lifecycle state transitions are not centrally coordinated.
  • In systems managing many nodes, this results in fragmented lifecycle handling, uncoordinated state transitions, and the absence of a single authoritative component responsible for global system state—an important weakness on resource‑constrained platforms.

2.4 Mismatch Between Robot Missions and Lifecycle Semantics

ROS 2 lifecycle states are intentionally minimal and low‑level, while production robots operate in mission‑level modes such as standby or navigation.

  • Without centralized orchestration, mapping mission‑level behavior to coordinated lifecycle transitions across multiple nodes becomes error‑prone and difficult to validate, especially on low‑end SoCs where deterministic behavior is critical.

💡 Summary
On low‑end embedded platforms, the Python‑based launch system introduces overhead and non‑determinism during boot and state transitions. In our evaluated setup, these limitations were not sufficiently mitigated through launch configuration alone. This motivated a native and deterministic lifecycle orchestration approach.

3. Architecture & LifecycleManager - "Solution"

A Modular Solution for Complex Systems

The Lifecycle Manager is a multi-threaded C++ ROS 2 node that acts as a centralized lifecycle coordinator. It utilizes a specialized Dual-Thread Architecture:

  • Spin Thread: Dedicated to handling ROS 2 communications and service callbacks.
  • Main Thread: Manages the core orchestration loop, including package spawning and the processQueue() mechanism. This non-blocking queue ensures that state transition requests are serialized and processed deterministically.

It operates as a ROS 2-native orchestration component within a standard ROS 2 system, while avoiding dependence on the Python-based launch path in the evaluated deployment mode.

The system is structured around five core modules:

flowchart TD
    App["<b>APPLICATION LAYER</b><br/>(Requests device state changes)"]
    YAML["<b>Configuration YAML</b><br/>(Source of Truth)"]

    subgraph Manager ["LIFECYCLE MANAGER (Native C++)"]
        direction TB
        SL["Service Layer<br/>(Queue Manager)"]
        Conf["Configuration<br/>(YAML Parser)"]
        Core["Orchestration Core<br/>(Coordinates Launcher & Engine)"]
        NL["Node Launcher<br/>(fork/exec/SIGCHLD)"]
        TE["Transition Engine<br/>(State Machine Logic)"]
        LC["Lifecycle Client<br/>(Service Interface)"]
        
        SL --> Core
        Conf --> Core
        Core --> NL
        Core --> TE
        TE --> LC
    end

    subgraph Nodes ["MANAGED ROS 2 NODES"]
        direction LR
        NA["Node A"]
        NB["Node B"]
        NN["Node N"]
    end

    App -- "ROS 2 Service" --> SL
    YAML --> Conf
    
    NL -- "Native Execution" --> Nodes
    LC -- "Get/ChangeState" --> Nodes

    style App fill:#f8f9fa,stroke:#343a40,stroke-width:2px
    style Manager fill:#f8f9fa,stroke:#343a40,stroke-width:2px
    style Nodes fill:#f8f9fa,stroke:#343a40,stroke-width:2px
    style SL fill:#fff,stroke:#343a40
    style Conf fill:#fff,stroke:#343a40
    style Core fill:#fff,stroke:#343a40
    style TE fill:#fff,stroke:#343a40
    style NL fill:#fff,stroke:#343a40
    style LC fill:#fff,stroke:#343a40
    style YAML fill:#fff,stroke:#343a40,stroke-width:2px
    style NA fill:#fff,stroke:#343a40
    style NB fill:#fff,stroke:#343a40
    style NN fill:#fff,stroke:#343a40
Loading
  • Service Layer – Exposes the /lifecycle_transition_device ROS 2 service and manages a thread-safe work queue. It supports string-based state transition requests (e.g., "NORMAL", "SLEEP") for improved human readability and CLI usability.
  • Config Engine – A centralized YAML parser that serves as the single source of truth. It supports professional name-based device state definitions, removing the need for fragile numeric indexing.
  • Transition Engine – The central orchestrator that coordinates complex lifecycle state machines across multiple packages. It implements the serialization of state transition requests to prevent race conditions during concurrent updates.
  • Node Launcher – Handles native process spawning via POSIX fork/exec and monitors child process health using SIGCHLD. It manages dynamic path resolution and per-process log redirection.
    • Benchmarking Mode: Includes a specialized execution path for legacy .py/.sh launch scripts (via system()), enabling deterministic A/B testing against standard Python launches.
  • Lifecycle Client – Interfaces with managed nodes using standard ROS 2 GetState and ChangeState services, featuring robust retry mechanisms and health monitoring.

Architecture Independence – By relying exclusively on POSIX standard system calls (fork, execvp, sigaction) and standard ROS 2 APIs, the Manager is designed to be portable across common Linux targets such as ARM64 and x86_64, subject to standard ROS 2 and platform integration constraints. This helps maintain consistent orchestration behavior across development and deployment platforms.

📊 Architecture Comparison

The following diagram illustrates the fundamental architectural shift from a heavy Python interpreter to our lightweight C++ native orchestrator.

flowchart TD
    subgraph Old [❌ Before: Python-Based ros2 launch]
        P_Interpreter["Python Interpreter (Heavy Base RAM)"] --> P_Launch{"launch.py"}
        P_Launch -- "1. High-Overhead Launch" --> P_N1[ROS 2 Node Process A]
        P_Launch -- "2. High-Overhead Launch" --> P_N2[ROS 2 Node Process B]
        P_Launch -- "3. High-Overhead Launch" --> P_N3[ROS 2 Node Process C]
        P_Note["🚨 Sequential Bottleneck & OOM"] -.-> P_Launch
    end

    subgraph New [✅ After: C++ Lifecycle Manager]
        C_Manager{"C++ LifecycleManager (Native OS Process, Ultra-Low RAM)"}
        C_Manager == "Concurrent fork() & execvp()" ==> C_N1[Node Process A]
        C_Manager == "Concurrent fork() & execvp()" ==> C_N2[Node Process B]
        C_Manager == "Concurrent fork() & execvp()" ==> C_N3[Node Process C]
        C_N1 -. "Lifecycle State: Active" .-> C_Manager
        C_N2 -. "Lifecycle State: Active" .-> C_Manager
        C_N3 -. "Lifecycle State: Active" .-> C_Manager
        C_Note["💡 Safe Concurrency & Determinism"] -.-> C_Manager
    end
    
    style P_Interpreter fill:#ffebee,stroke:#c62828,stroke-width:2px
    style P_Launch fill:#ffcdd2,stroke:#c62828,stroke-width:2px
    style C_Manager fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
Loading

⚙️ YAML-Driven Configuration

All orchestration behavior is defined declaratively in a single YAML file:

# Example: Configuration with Professional State Names
LIFECYCLE_MANAGER_CONFIG:
  DEVICE_STATE_NAMES: ["NORMAL", "SLEEP", "POWERSAVE"]
  USE_LAUNCH_SCRIPT: false
  NODE_TRANSITION_STRATEGY: "parallel"

PACKAGE_slam_package:
  PACKAGE_ENABLE: true
  NODE_slam_node:
    EXECUTABLE: slam_node
    ARGUMENT: ["--param_a", "value_a"]
    DEPENDENCY: ["lidar_package,lidar_node"]
    DEVICE_INIT: ACTIVE
    DEVICE_STATE_NORMAL: ACTIVE
    DEVICE_STATE_SLEEP: INACTIVE
    DEVICE_STATE_POWERSAVE: FINALIZED

Key configuration capabilities:

  • Launch mode (USE_LAUNCH_SCRIPT): Selects between native binary spawning (fork/exec) or script-based execution (benchmarking)
  • Transition strategy (NODE_TRANSITION_STRATEGY): Parallel (multi-threaded) or sequential execution
  • Executable arguments (ARGUMENT): Command-line arguments for the native process
  • Dependency declaration (DEPENDENCY): Inter-node startup ordering and readiness polling
  • Initialization target (DEVICE_INIT): Specific lifecycle state for initial boot
  • Device state mapping (DEVICE_STATE_<NAME>): Per-node lifecycle targets for each robot mission state

🔁 Multi-Step Lifecycle State Machine

The ROS 2 lifecycle standard does not allow direct transitions between certain primary states. The Lifecycle Manager resolves all intermediate steps automatically:

UNCONFIGURED ➔ ACTIVE     : Configure ➔ Activate  (two-step)
ACTIVE ➔ UNCONFIGURED     : Deactivate ➔ Cleanup  (two-step)
UNCONFIGURED ➔ INACTIVE   : Configure
INACTIVE ➔ ACTIVE         : Activate
ACTIVE ➔ INACTIVE         : Deactivate
Any state ➔ FINALIZED     : Appropriate shutdown transition

The application layer simply declares a target state; the Lifecycle Manager resolves and executes all intermediate transitions transparently, each wrapped with configurable retry and timeout policies.

🤖 Device State Abstraction

To bridge the semantic gap between robot missions and low-level lifecycle states, the Lifecycle Manager introduces a "Device State" abstraction.

Node \ Device State NORMAL SLEEP POWERSAVE
slam_node ACTIVE INACTIVE FINALIZED
lidar_node ACTIVE INACTIVE FINALIZED
navigation_node ACTIVE INACTIVE FINALIZED
motor_node ACTIVE INACTIVE FINALIZED
camera_node ACTIVE ACTIVE INACTIVE
diagnostic_node ACTIVE ACTIVE ACTIVE

To switch the robot from "NORMAL" to "SLEEP", just call:

ros2 service call /lifecycle_transition_device lifecycle_manager_msgs/srv/TransitionDevice "{request: 'SLEEP'}"

This single call automatically transitions each node to its matching target state — navigation and motor stop, while camera and diagnostics stay running. This design completely decouples mission logic from lifecycle management.

4. Deterministic Boot Flow - "Deep-dive"

Technical Logic from Initialization to Operation

The Lifecycle Manager follows a rigorous, deterministic sequence to ensure all nodes are prepared and synchronized.

By identifying independent node groups at runtime from YAML dependency declarations (e.g., DEPENDENCY: ["pkg_name,node_name"]), the system initializes multiple packages concurrently — optimizing the theoretical boot time from O(N) sequential initialization to O(Depth(G)), where Depth(G) is the longest dependency path in the package graph. In other words, boot time becomes bounded by the longest dependency chain rather than the total number of nodes.

flowchart TD
    Start("[ SYSTEM STARTUP ]") --> YAML
    
    YAML["<b>YAML Configuration</b><br/>(Source of Truth)"] --> Exec
    Exec["<b>Execution Strategy</b><br/>(Parallel vs Sequential)"] --> Path
    
    subgraph Path ["[ ORCHESTRATOR PATH ]  -  (Parallel / Seq Loop)"]
        direction TB
        Check["<b>Check if Enabled</b><br/>(f_packageLaunch flag)"] --> Launch
        Launch["<b>Package Launch</b><br/>(Native fork/exec)"] --> Dep
        Dep["<b>Dependency & State Check</b><br/>(GetState + Dep Polling)"] --> Trans
        Trans["<b>State Transition</b><br/>(ChangeState Client)"]
    end
    
    Path --> Ready("[ SYSTEM READY ]")

    style Path fill:#f8f9fa,stroke:#343a40,stroke-width:2px,stroke-dasharray: 5 5
    style Start fill:#e7f3ff,stroke:#007bff,stroke-width:2px
    style Ready fill:#d4edda,stroke:#28a745,stroke-width:2px
    
    style YAML fill:#fff,stroke:#343a40,stroke-width:1px
    style Exec fill:#fff,stroke:#343a40,stroke-width:1px
    style Check fill:#fff,stroke:#343a40,stroke-width:1px
    style Launch fill:#fff,stroke:#343a40,stroke-width:1px
    style Dep fill:#fff,stroke:#343a40,stroke-width:1px
    style Trans fill:#fff,stroke:#343a40,stroke-width:1px
Loading

Startup Sequence (Step-by-Step)

  1. Load YAML Configuration — Reads all package/node definitions, dependencies, and device-state mappings from a single YAML file.
  2. Select Execution Strategy — Determines parallel (multi-threaded) or sequential mode per YAML configuration.
  3. Native Process Spawning — Each package binary is launched via fork/exec with executable paths resolved dynamically by ament_index_cpp. Per-process log files are created with timestamps.
  4. Dependency Wait — For each node, polls the managed-state of declared dependency nodes until they reach ACTIVE (30-second timeout).
  5. Lifecycle State Polling — Calls GetState service with retry until the node responds, confirming it is alive and ready for state transitions.
  6. Initial State Transition — Applies the DEVICE_INIT target lifecycle state via the multi-step state machine (e.g., triggering Configure ➔ Activate for an ACTIVE target).

After all nodes reach their initial states, the system enters the operational phase — waiting for Device State requests via /lifecycle_transition_device and applying per-node lifecycle targets through the TransitionEngine.

5. Metrics & Validation - "Validation"

This section validates the impact of Deterministic Lifecycle Manager using measurements collected on a pre-production commercial cleaning robot platform publicly showcased at IFA 2025.

For evaluation purposes, the product's software stack was ported to ROS 2, system interfaces were redesigned, and the C++ DLM was deployed directly on the target hardware. The goal was realistic system-level evaluation under severe production constraints (memory pressure and strict boot-time requirements). All measurements were performed on identical hardware using the same ROS 2 node set.

🧪 Test Environment

ITEM Specification
HW Platform (AP) LG DQ1 (Cortex-A53x4 @ 1GHz)
RAM 1GB
eMMC 4GB
ROS 2 Distribution ROS 2 Humble
Managed ROS 2 Nodes 14 nodes + lifecycle node
OS Yocto based ROS 2
Yocto Version Kirkstone

🚀 Boot Configurations

Two boot configurations were evaluated:

  • Baseline: ROS 2 default Python-based launch ROS 2 nodes were started using the standard ros2 launch workflow, which loads the Python interpreter and initializes the launch framework during system boot.
  • Modified: Binary-based launch using Deterministic Lifecycle Manager All ROS 2 nodes were executed directly as OS processes under DLM control, without involving the Python runtime.

No other system components or ROS 2 node implementations were changed between the two configurations.

📈 Results

The measured results are summarized in the data below.

TABLE I. STARTUP PERFORMANCE COMPARISON

Metric Python-based launch C++ DLM (Proposed)
Boot time (s) 59.96 ± 0.70 11.20 ± 0.68
Avg RAM during boot (MB) 228.95 ± 1.80 201.28 ± 8.59
Stable RAM 5 s after boot (MB) 441.97 ± 1.41 270.86 ± 1.83

Values are averaged over ten repeated runs (Mean ± Std. Dev.).

  • Boot completion time was reduced from 59.96s to 11.20s, corresponding to an 81.3% reduction.
  • Stable memory usage after startup decreased from 441.97MB to 270.86MB, corresponding to a 38.7% reduction.

📊 Performance Charts

Legacy Sequential Python Launch Proposed Parallel Native C++ DLM
Sequential Parallel

Left: Legacy Sequential Python Launch | Right: Proposed Parallel Native C++ DLM

📋 Chart Data Summary

  • ⏱️ Execution Time Comparison (Boot Speed)

    • Python Launch: 59.96s
    • Binary (C++): 11.20s
    • Improvement: ↓ 81.3% (59.96s ➔ 11.20s)
  • 💾 Stable Memory Usage Comparison (RAM)

    • Python Launch: 441.97MB
    • Binary (C++): 270.86MB
    • Improvement: ↓ 38.7% (441.97MB ➔ 270.86MB)

🧪 Reproducibility

All experiments were conducted on identical hardware and software configurations.

  • Same nodes and execution graph
  • Same workload
  • Only the orchestration mechanism was changed (Python-based ros2 launch vs LifecycleManager)

Detailed configuration and setup are available in this repository.

A side-by-side boot comparison (Legacy Python vs. Native C++ DLM) is shown in the Performance Charts section above.

💡 Benchmark Reproducibility:
The 81.3% performance gain was verified using the built-in Benchmark Mode. By toggling the use_launch_script flag in the same LifecycleManager instance, we compared identical node sets launched via Python scripts vs. direct C++ native spawning, supporting the interpretation that a large portion of the improvement is attributable to orchestration-path differences.

Conclusion: By removing Python from the evaluated runtime path, startup memory pressure was reduced, and OOM events observed in the baseline configuration were not reproduced in the evaluated DLM configuration. These improvements were achieved without modifying the ROS 2 nodes themselves and resulted in a stable and repeatable boot sequence on the target hardware.

6. Source, Build & License - "Open-Source Status"

⚙️ Technical Requirements

  • Architecture: x86_64 (Development/PC) and ARM64/AArch64 (Embedded Target)
  • OS: Ubuntu 22.04 (Jammy) or later / Linux (Yocto-based)
  • ROS 2: Humble, Iron, or Jazzy
  • Compiler: C++17 or higher (Required for <filesystem> and modern C++ features)
  • Dependencies: rclcpp, lifecycle_msgs, lifecycle_manager_msgs, yaml-cpp

🛠️ Build / Run Status

The full implementation is not yet publicly available in this repository.

Build and run instructions will be added once the source release is completed.

Currently available in this repository:

  • architecture documentation
  • YAML configuration examples
  • measurement methodology
  • empirical validation results

📝 License & Source Code Access

This project is licensed under the Apache License 2.0, allowing internal commercial use as well as future open-source contributions.

As described at the top of this document, this repository currently provides architectural documentation, configuration examples, and empirical validation results that fully describe the execution model and lifecycle behavior of the system.

The complete C++ source code is planned to be released under the same license following completion of internal compliance procedures.

About

A C++ deterministic lifecycle orchestrator optimized for ROS 2 embedded systems.

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors