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:
Runtime-Only (default): Traditional ROS 2 graph introspection
Hybrid (recommended): Manifest as source of truth + runtime linking
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:
Discovery: Gateway discovers running ROS 2 nodes
Matching: For each manifest app, checks
ros_bindingconfigurationLinking: If match found, copies runtime resources (topics, services, actions)
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 namespaceTopic 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 allwarn(default): Log warning, include nodes as orphanserror: Fail startup if orphan nodes detectedinclude_as_orphan: Include withsource: "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 |
|---|---|---|
|
List all apps |
0.1.0 |
|
Get app capabilities |
0.1.0 |
|
Get app topic data |
0.1.0 |
|
List app operations |
0.1.0 |
|
List app configurations |
0.1.0 |
|
List all functions |
0.1.0 |
|
Get function capabilities |
0.1.0 |
|
List function host apps |
0.1.0 |
|
Aggregated data from hosts |
0.1.0 |
|
Aggregated operations |
0.1.0 |
Next Steps
Manifest Schema Reference - Complete YAML schema reference
Migration Guide: Runtime to Hybrid Mode - Migrate from runtime-only mode
Getting Started - Basic gateway setup