Manifest-Based Discovery

This tutorial explains how to use manifest files to define your ROS 2 system structure for SOVD discovery.

Introduction

The ros2_medkit gateway supports three discovery modes:

  1. Runtime-Only (default): Traditional ROS 2 graph introspection

  2. Hybrid (recommended): Manifest as source of truth + runtime linking

  3. Manifest-Only: Only expose manifest-declared entities

Hybrid mode is recommended because it:

  • Provides stable entity IDs that don’t change across restarts

  • Enables semantic groupings (areas, functions) for better organization

  • Preserves runtime data (topic values, service calls)

  • Allows documentation and metadata in entity definitions

  • Supports offline detection for apps not currently running

Discovery Modes Comparison

Feature

Runtime-Only

Hybrid

Manifest-Only

Entity IDs

Derived from ROS graph

Stable (from manifest)

Stable (from manifest)

Live topics/services

✅ Yes

✅ Yes

❌ No

Custom areas

❌ No (derived from namespaces)

✅ Yes

✅ Yes

Apps entity type

✅ Yes (ROS nodes → Apps)

✅ Yes

✅ Yes

Functions entity type

❌ No

✅ Yes

✅ Yes

Offline detection

❌ No

✅ Yes

N/A

Writing Your First Manifest

Create a file named system_manifest.yaml:

# SOVD System Manifest
manifest_version: "1.0"

metadata:
  name: "my-robot"
  version: "1.0.0"
  description: "My Robot System"

# Define logical areas (subsystems)
areas:
  - id: perception
    name: "Perception"
    category: "sensor-processing"
    description: "Sensor data processing and fusion"

# Define hardware/virtual components
components:
  - id: lidar-sensor
    name: "LiDAR Sensor"
    type: "sensor"
    area: perception

# Define apps (map to ROS 2 nodes)
apps:
  - id: lidar-driver
    name: "LiDAR Driver"
    is_located_on: lidar-sensor
    ros_binding:
      node_name: velodyne_driver
      namespace: /sensors

# Define high-level functions
functions:
  - id: environment-sensing
    name: "Environment Sensing"
    hosted_by:
      - lidar-driver

Enabling Manifest Mode

Option 1: Using Launch File

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_medkit_gateway',
            executable='gateway_node',
            parameters=[{
                'manifest.enabled': True,
                'manifest.file_path': '/path/to/system_manifest.yaml',
                'manifest.mode': 'hybrid',
            }]
        )
    ])

Option 2: Using Parameter File

Add to your gateway_params.yaml:

ros2_medkit_gateway:
  ros__parameters:
    # ... existing parameters ...

    manifest:
      # Enable manifest-based discovery
      enabled: true

      # Path to manifest YAML file
      file_path: "/path/to/system_manifest.yaml"

      # Discovery mode: "runtime_only", "hybrid", or "manifest_only"
      mode: "hybrid"

      # Strict validation: fail on any validation error
      strict_validation: true

Then launch with:

ros2 run ros2_medkit_gateway gateway_node \
    --ros-args --params-file /path/to/gateway_params.yaml

Option 3: Command Line Parameters

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

Verifying the Configuration

Once the gateway is running with a manifest, you can verify the configuration via the REST API.

Check manifest status:

curl http://localhost:8080/api/v1/manifest/status

Expected response:

{
  "status": "active",
  "discovery_mode": "hybrid",
  "manifest_path": "/path/to/system_manifest.yaml",
  "statistics": {
    "areas_count": 1,
    "components_count": 1,
    "apps_count": 1,
    "functions_count": 1
  }
}

List apps:

curl http://localhost:8080/api/v1/apps

List functions:

curl http://localhost:8080/api/v1/functions

Understanding Runtime Linking

In hybrid mode, manifest apps are automatically linked to running ROS 2 nodes. The linking process:

  1. Discovery: Gateway discovers running ROS 2 nodes

  2. Matching: For each manifest app, checks ros_binding configuration

  3. Linking: If match found, copies runtime resources (topics, services, actions)

  4. Status: Apps with matched nodes are marked is_online: true

ROS Binding Configuration

The ros_binding section specifies how to match an app to a ROS 2 node:

ros_binding:
  # Match by node name
  node_name: "velodyne_driver"

  # Match within a specific namespace (optional)
  namespace: "/sensors"

  # Or match by topic namespace prefix (alternative)
  topic_namespace: "/sensors/lidar"

Match Types:

  • Exact match (default): Node name and namespace must match exactly

  • Wildcard namespace: Use namespace: "*" to match any namespace

  • Topic namespace: Match nodes by their topic prefix

# Example: Match node in any namespace
apps:
  - id: camera-driver
    name: "Camera Driver"
    ros_binding:
      node_name: usb_cam
      namespace: "*"

# Example: Match by topic namespace
apps:
  - id: lidar-processing
    name: "LiDAR Processing"
    ros_binding:
      topic_namespace: "/perception/lidar"

Checking Linking Status

Check which apps are online:

curl http://localhost:8080/api/v1/apps | jq '.[] | {id, name, is_online}'

Example response:

{"id": "lidar-driver", "name": "LiDAR Driver", "is_online": true}
{"id": "camera-driver", "name": "Camera Driver", "is_online": false}

Entity Hierarchy

The manifest defines a hierarchical structure:

Areas (logical/physical groupings)
└── Components (hardware/virtual units)
    └── Apps (software applications)
        └── Data (topics)
        └── Operations (services/actions)
        └── Configurations (parameters)

Functions (cross-cutting capabilities)
└── Apps (hosted by)

Areas group related components by function or location:

areas:
  - id: perception
    name: "Perception Subsystem"
    subareas:
      - id: lidar-processing
        name: "LiDAR Processing"

Components represent hardware or virtual units:

components:
  - id: main-computer
    name: "Main Computer"
    type: "controller"
    area: perception
    subcomponents:
      - id: gpu-unit
        name: "GPU Processing Unit"

Apps are software applications (typically ROS 2 nodes):

apps:
  - id: slam-node
    name: "SLAM Node"
    is_located_on: main-computer
    depends_on:
      - lidar-driver
      - imu-driver
    ros_binding:
      node_name: slam_toolbox

Functions are high-level capabilities spanning multiple apps:

functions:
  - id: autonomous-navigation
    name: "Autonomous Navigation"
    hosted_by:
      - planner-node
      - controller-node
      - localization-node

Handling Unmanifested Nodes

In hybrid mode, the gateway may discover ROS 2 nodes that aren’t declared in the manifest. The config.unmanifested_nodes setting controls this:

config:
  # Options: ignore, warn, error, include_as_orphan
  unmanifested_nodes: warn

Policies:

  • ignore: Don’t expose unmanifested nodes at all

  • warn (default): Log warning, include nodes as orphans

  • error: Fail startup if orphan nodes detected

  • include_as_orphan: Include with source: "orphan"

Hot Reloading

Reload the manifest without restarting the gateway:

curl -X POST http://localhost:8080/api/v1/manifest/reload

This re-parses the manifest file and re-links apps to nodes.

REST API Endpoints

Manifest mode adds the following endpoints:

Endpoint

Description

Since

GET /apps

List all apps

0.1.0

GET /apps/{id}

Get app capabilities

0.1.0

GET /apps/{id}/data

Get app topic data

0.1.0

GET /apps/{id}/operations

List app operations

0.1.0

GET /apps/{id}/configurations

List app configurations

0.1.0

GET /functions

List all functions

0.1.0

GET /functions/{id}

Get function capabilities

0.1.0

GET /functions/{id}/hosts

List function host apps

0.1.0

GET /functions/{id}/data

Aggregated data from hosts

0.1.0

GET /functions/{id}/operations

Aggregated operations

0.1.0

Next Steps