Migration Guide: Runtime to Hybrid Mode

This guide helps you migrate from runtime-only discovery to hybrid mode, enabling stable entity IDs, semantic groupings, and offline detection.

Overview

Runtime-only mode (default) automatically discovers:

  • ROS 2 nodes → Apps

  • Namespace groupings → Synthetic Components

  • Top-level namespaces → Areas

  • Topics, services, actions → Data, operations

Hybrid mode adds:

  • Stable, meaningful entity IDs

  • Apps entity type (software applications)

  • Functions entity type (high-level capabilities)

  • Offline detection for apps

  • Custom metadata and descriptions

The migration process creates a manifest that maps your existing ROS 2 nodes to the SOVD entity model while preserving runtime data access.

Step 1: Audit Your System

First, understand your current ROS 2 system structure.

List all running nodes:

ros2 node list

Example output:

/amcl
/bt_navigator
/controller_server
/ld08_driver
/map_server
/planner_server
/robot_state_publisher
/turtlebot3_node

For each node, get detailed info:

ros2 node info /amcl

Note down:

  • Node name and namespace

  • Published/subscribed topics

  • Provided services and actions

  • Purpose and category

Create a spreadsheet or document with columns:

Node Name

Namespace

Category

Purpose

amcl

/

localization

Adaptive Monte Carlo Localization

planner_server

/

navigation

Global path planning

controller_server

/

navigation

Local trajectory tracking

Step 2: Define Areas

Group your nodes into logical subsystems. Common groupings:

  • By function: perception, localization, navigation, control

  • By hardware: sensors, actuators, compute

  • By namespace: ROS 2 namespace structure

Example area mapping:

areas:
  - id: perception
    name: "Perception"
    category: "sensor-processing"
    description: "Sensor data acquisition and processing"

  - id: localization
    name: "Localization"
    category: "state-estimation"
    description: "Robot pose estimation"

  - id: navigation
    name: "Navigation"
    category: "motion-planning"
    description: "Path planning and execution"

  - id: control
    name: "Control"
    category: "motion-control"
    description: "Low-level motor control"

Tip

Start with broad categories and refine later. You can nest areas using subareas if needed.

Step 3: Define Components

Map physical and virtual hardware. Components represent:

  • Physical sensors (LiDAR, cameras, IMU)

  • Actuators (motors, grippers)

  • Compute units (main computer, edge devices)

  • Virtual units (containers, processes)

Example component mapping:

components:
  - id: lidar-sensor
    name: "LiDAR Sensor"
    type: "sensor"
    area: perception
    description: "360° laser range finder"

  - id: main-computer
    name: "Main Computer"
    type: "controller"
    area: control
    description: "Raspberry Pi 4 running ROS 2"

  - id: opencr-board
    name: "OpenCR Board"
    type: "controller"
    area: control
    description: "Motor controller board"

Step 4: Define Apps

Create an app entry for each ROS 2 node. Key decisions:

  1. Choose a stable ID: Use lowercase with hyphens (e.g., lidar-driver)

  2. Set human-readable name: Clear, descriptive name

  3. Configure ros_binding: How to match the ROS 2 node

  4. Set component: Which component hosts this app

  5. Set depends_on: Dependencies on other apps

Example app mapping:

apps:
  # Node: /ld08_driver
  - id: lidar-driver
    name: "LiDAR Driver"
    category: "driver"
    is_located_on: lidar-sensor
    ros_binding:
      node_name: ld08_driver

  # Node: /amcl
  - id: amcl-node
    name: "AMCL Localization"
    category: "localization"
    is_located_on: main-computer
    depends_on:
      - lidar-driver
    ros_binding:
      node_name: amcl

  # Node: /planner_server
  - id: planner-server
    name: "Planner Server"
    category: "navigation"
    is_located_on: main-computer
    depends_on:
      - amcl-node
    ros_binding:
      node_name: planner_server

Handling namespaced nodes:

# Node: /robot1/lidar_driver
- id: robot1-lidar-driver
  name: "Robot 1 LiDAR Driver"
  ros_binding:
    node_name: lidar_driver
    namespace: /robot1

# Match node in any namespace
- id: generic-teleop
  name: "Teleop Controller"
  ros_binding:
    node_name: teleop_keyboard
    namespace: "*"

Step 5: Define Functions

Identify high-level capabilities your robot provides:

  • What can your robot do as a user?

  • Which apps work together for each capability?

Example function definitions:

functions:
  - id: autonomous-navigation
    name: "Autonomous Navigation"
    category: "mobility"
    description: "Navigate autonomously to goals"
    hosted_by:
      - amcl-node
      - planner-server
      - controller-server
      - bt-navigator

  - id: localization
    name: "Localization"
    category: "state-estimation"
    description: "Determine robot position on map"
    hosted_by:
      - amcl-node
      - map-server

  - id: perception
    name: "Environment Perception"
    category: "sensing"
    hosted_by:
      - lidar-driver

Step 6: Create the Manifest File

Combine all sections into a complete manifest:

manifest_version: "1.0"

metadata:
  name: "my-robot"
  version: "1.0.0"
  description: "My robot system manifest"

config:
  unmanifested_nodes: warn
  inherit_runtime_resources: true

areas:
  # ... your area definitions

components:
  # ... your component definitions

apps:
  # ... your app definitions

functions:
  # ... your function definitions

Save as system_manifest.yaml in your config directory.

Step 7: Test in Hybrid Mode

  1. Start your ROS 2 system as normal

  2. Start the gateway with manifest:

    ros2 run ros2_medkit_gateway gateway_node --ros-args \
        -p manifest.enabled:=true \
        -p manifest.file_path:=/path/to/system_manifest.yaml \
        -p manifest.mode:=hybrid
    
  3. Check manifest status:

    curl http://localhost:8080/api/v1/manifest/status | jq
    
  4. Verify apps are linked:

    curl http://localhost:8080/api/v1/apps | jq '.[] | {id, name, is_online}'
    
  5. Check for orphan nodes (warnings in gateway logs):

    ros2 run ros2_medkit_gateway gateway_node ... 2>&1 | grep -i orphan
    

Step 8: Iterate and Refine

Based on testing results:

App not linking to node:

  • Check ros_binding.node_name spelling

  • Verify namespace matches (use ros2 node list to confirm)

  • Try wildcard namespace: namespace: "*"

  • Check gateway logs for matching attempts

Orphan nodes appearing:

  • Add app entries for missing nodes

  • Or set config.unmanifested_nodes: ignore to hide them

Missing data/operations:

  • Ensure config.inherit_runtime_resources: true

  • Check that the bound node has the expected topics/services

Refining the manifest:

  • Add descriptions to improve documentation

  • Add tags for filtering

  • Adjust categories for better organization

  • Define dependencies between apps

Validation Checklist

Before finalizing your manifest:

[ ] All required fields present (manifest_version, entity ids and names)
[ ] All area references are valid (component.area, subarea.parent)
[ ] All component references are valid (app.component)
[ ] All app references are valid (depends_on, function.hosted_by)
[ ] IDs are unique within each entity type
[ ] ros_binding configured for all apps that map to ROS nodes
[ ] Functions have at least one app in hosted_by

Run validation:

# Start gateway with strict validation
ros2 run ros2_medkit_gateway gateway_node --ros-args \
    -p manifest.enabled:=true \
    -p manifest.file_path:=/path/to/system_manifest.yaml \
    -p manifest.strict_validation:=true

Common Issues

“App X not found for function Y”

The hosted_by list references an app ID that doesn’t exist.

# Wrong - typo in app ID
functions:
  - id: nav-function
    hosted_by:
      - amcl_node  # Should be "amcl-node"

“Component X not found for app Y”

The component references a component that doesn’t exist.

# Wrong - component doesn't exist
apps:
  - id: my-app
    component: nonexistent-component

“Duplicate ID: X”

Two entities have the same ID.

# Wrong - duplicate IDs
components:
  - id: sensor
    name: "LiDAR"
  - id: sensor  # Duplicate!
    name: "Camera"

Node not linking (is_online: false)

Common causes:

  1. Node name mismatch (check exact spelling)

  2. Namespace mismatch (try wildcard)

  3. Node not running when gateway starts

  4. Node in different ROS domain

Debug with:

# List actual nodes
ros2 node list

# Check gateway logs
ros2 run ros2_medkit_gateway gateway_node ... 2>&1 | grep -i link

Best Practices

  1. Start simple: Begin with just apps and expand to functions later

  2. Use consistent naming: Follow a pattern for IDs (e.g., {component}-{function})

  3. Document as you go: Add descriptions while the context is fresh

  4. Version your manifest: Use semantic versioning in metadata

  5. Keep manifest in source control: Track changes alongside code

  6. Test incrementally: Add a few entities, test, repeat

  7. Use validation: Always run with strict_validation: true initially

Next Steps