Discovery Options Reference
This document describes configuration options for the gateway’s discovery system. The discovery system maps ROS 2 graph entities (nodes, topics, services, actions) to SOVD entities (areas, components, apps).
Discovery Modes
The gateway supports three discovery modes, controlled by the discovery.mode parameter:
Mode |
Description |
|---|---|
|
Use ROS 2 graph introspection only. Nodes are discovered at runtime and mapped to SOVD entities using heuristic rules. (default) |
|
Only expose entities declared in the manifest file. Runtime discovery is disabled. |
|
Manifest defines the structure, runtime links to discovered nodes. |
Runtime Discovery Options
When using runtime_only or hybrid mode, the following options control
how ROS 2 nodes are mapped to SOVD entities.
Synthetic Components
discovery:
runtime:
create_synthetic_components: true
grouping_strategy: "namespace"
synthetic_component_name_pattern: "{area}"
When create_synthetic_components is true:
Components are created as logical groupings of Apps
grouping_strategy: "namespace"groups nodes by their first namespace segmentsynthetic_component_name_patterndefines the component ID format
Note
When create_synthetic_areas is false, the {area} placeholder in
synthetic_component_name_pattern still resolves to the namespace
segment used as the component grouping key - it does not require areas
to be enabled.
Synthetic Areas
discovery:
runtime:
create_synthetic_areas: true
When create_synthetic_areas is true (the default):
Areas are created from ROS 2 namespace segments (e.g.,
/powertrainbecomes areapowertrain)Components and Apps are organized under these Areas
When create_synthetic_areas is false:
No Areas are created from namespaces
The entity tree is flat - Components are top-level entities
This is useful for simple robots (e.g., TurtleBot3) where an area hierarchy adds unnecessary nesting
# Flat entity tree - no areas
discovery:
runtime:
create_synthetic_areas: false
Topic-Only Namespaces
Some ROS 2 systems have topics published to namespaces without any associated nodes. This is common with:
Isaac Sim and other simulators
External bridges (MQTT, Zenoh, ROS 1)
Dead/orphaned topics from crashed processes
The topic_only_policy controls how these namespaces are handled:
discovery:
runtime:
topic_only_policy: "create_component"
min_topics_for_component: 1
Policy |
Description |
|---|---|
|
Don’t create any entities for topic-only namespaces. Use this to reduce noise from orphaned topics. |
|
Create a Component with |
|
Only create the Area, but don’t create a Component. Useful when you want the namespace visible but not as a component. |
The min_topics_for_component parameter (default: 1) sets the minimum number
of topics required before creating a component. This can filter out namespaces
with only a few stray topics.
Note
create_area_only has no effect when create_synthetic_areas: false -
use ignore instead.
Merge Pipeline (Hybrid Mode)
In hybrid mode, the gateway uses a layered merge pipeline to combine entities from multiple sources. Three layers contribute entities independently:
ManifestLayer - entities declared in the YAML manifest (highest priority for identity/hierarchy)
RuntimeLayer - entities discovered from the ROS 2 graph (highest priority for live data/status)
PluginLayer - entities contributed by
IntrospectionProviderplugins
Each layer’s contribution is merged per field-group with configurable policies.
Layer Enable/Disable
Individual layers can be disabled without changing the discovery mode. This is useful when running beacon plugins as the sole enrichment source (disable manifest and/or runtime layers, keep only plugins):
discovery:
manifest:
enabled: true # Default: true
runtime:
enabled: true # Default: true
When a layer is disabled, its entities are not discovered and its merge contributions are skipped. Plugins always run regardless of these flags.
Field Groups
Field Group |
Contents |
|---|---|
|
id, name, translation_id, description, tags |
|
area, component_id, parent references, depends_on, hosts |
|
topics, services, actions |
|
is_online, bound_fqn |
|
source, ros_binding, external, x-medkit extensions, custom metadata fields |
Merge Policies
Each layer declares a policy per field-group:
Policy |
Behavior |
|---|---|
|
This layer’s value wins over lower-priority layers. |
|
Fill empty fields only; don’t override existing values. |
|
Use only if no other layer provides the value. |
Defaults:
Manifest:
authoritativefor identity/hierarchy/metadata,enrichmentfor live_data,fallbackfor statusRuntime:
authoritativefor live_data/status,enrichmentfor metadata,fallbackfor identity/hierarchy
Override per-layer policies in gateway_params.yaml. Empty string means
“use layer default”. Policy values are case-sensitive and must be lowercase
(authoritative, enrichment, fallback):
discovery:
merge_pipeline:
layers:
manifest:
identity: "" # authoritative (default)
hierarchy: "" # authoritative (default)
live_data: "" # enrichment (default)
status: "" # fallback (default)
metadata: "" # authoritative (default)
runtime:
identity: "" # fallback (default)
hierarchy: "" # fallback (default)
live_data: "" # authoritative (default)
status: "" # authoritative (default)
metadata: "" # enrichment (default)
Gap-Fill
In hybrid mode, the runtime layer can create heuristic entities for namespaces not covered by the manifest. Gap-fill controls what the runtime layer is allowed to create:
discovery:
merge_pipeline:
gap_fill:
allow_heuristic_areas: true
allow_heuristic_components: true
allow_heuristic_apps: true
allow_heuristic_functions: false
# namespace_blacklist: ["/rosout"]
# namespace_whitelist: []
Parameter |
Default |
Description |
|---|---|---|
|
|
Create areas from namespaces not in manifest. |
|
|
Create synthetic components for unmanifested namespaces. |
|
|
Create apps for nodes without manifest |
|
|
Create heuristic functions (not recommended). |
|
|
Namespaces excluded from gap-fill (e.g., |
|
|
If non-empty, only these namespaces are eligible for gap-fill. |
Health Endpoint
GET /api/v1/health includes a discovery section in hybrid mode with
pipeline stats, linking results, and merge warnings:
{
"status": "healthy",
"discovery": {
"mode": "hybrid",
"strategy": "hybrid",
"pipeline": {
"layers": ["manifest", "runtime", "plugin"],
"total_entities": 6,
"enriched_count": 5,
"conflict_count": 0,
"conflicts": [],
"id_collisions": 0,
"filtered_by_gap_fill": 0
},
"linking": {
"linked_count": 5,
"orphan_count": 1,
"binding_conflicts": 0
}
}
}
Configuration Example
Complete YAML configuration for runtime discovery:
ros2_medkit_gateway:
ros__parameters:
discovery:
mode: "runtime_only"
runtime:
# Create Areas from namespace segments
create_synthetic_areas: true
# Group Apps into Components by namespace
create_synthetic_components: true
grouping_strategy: "namespace"
synthetic_component_name_pattern: "{area}"
# Handle topic-only namespaces
topic_only_policy: "create_component"
min_topics_for_component: 2 # Require at least 2 topics
# Merge pipeline (hybrid mode only)
merge_pipeline:
gap_fill:
allow_heuristic_areas: true
allow_heuristic_components: true
allow_heuristic_apps: true
namespace_blacklist: ["/rosout"]
Command Line Override
Override discovery options via command line:
ros2 launch ros2_medkit_gateway gateway.launch.py \
discovery.runtime.topic_only_policy:="ignore" \
discovery.runtime.min_topics_for_component:=3
Discovery Mechanism Selection
Scenario |
Recommended mechanism |
|---|---|
Quick start, no configuration needed |
|
Stable system with known topology |
|
Runtime metadata from nodes (fast updates, custom topics) |
TopicBeaconPlugin (push-based) |
Runtime metadata from nodes (no custom code, uses parameters) |
ParameterBeaconPlugin (pull-based) |
Structured topology with runtime enrichment |
|
Topic beacon vs parameter beacon:
Use TopicBeaconPlugin when nodes can publish
MedkitDiscoveryHintmessages to a shared topic. Best for low-latency updates and nodes that already have custom publishing logic.Use ParameterBeaconPlugin when nodes declare metadata as standard ROS 2 parameters. No custom publishing code needed - the plugin polls parameters automatically.
Both can run simultaneously. Each maintains a separate store, and both inject into the merge pipeline as enrichment layers.
Beacon Discovery Plugin (TopicBeaconPlugin)
The ros2_medkit_topic_beacon plugin enriches discovered entities with
push-based metadata from ROS 2 nodes. Nodes publish
ros2_medkit_msgs/msg/MedkitDiscoveryHint messages to
/ros2_medkit/discovery and the plugin injects the data into the merge
pipeline as an ENRICHMENT layer.
Configuration
ros2_medkit_gateway:
ros__parameters:
plugins: ["topic_beacon"]
plugins.topic_beacon.path: "/path/to/libtopic_beacon_plugin.so"
# ROS 2 topic to subscribe for beacon hints
# Default: "/ros2_medkit/discovery"
plugins.topic_beacon.topic: "/ros2_medkit/discovery"
# Soft TTL: hints older than this are marked STALE (seconds)
# Default: 10.0
plugins.topic_beacon.beacon_ttl_sec: 10.0
# Hard expiry: hints older than this are removed (seconds)
# Default: 300.0
plugins.topic_beacon.beacon_expiry_sec: 300.0
# Allow plugin to introduce entirely new entities not seen by other layers
# When false (recommended), only known entities are enriched
# Default: false
plugins.topic_beacon.allow_new_entities: false
# Maximum number of hints to keep in memory
# Default: 10000
plugins.topic_beacon.max_hints: 10000
# Rate limit for incoming beacon messages
# Default: 100
plugins.topic_beacon.max_messages_per_second: 100
Beacon Lifecycle
Each hint follows a soft TTL lifecycle based on the stamp field when
provided, otherwise based on reception time:
State |
Description |
|---|---|
ACTIVE |
Hint is within |
STALE |
Hint is past TTL but within |
EXPIRED |
Hint is past |
The x-medkit-topic-beacon vendor endpoint exposes current beacon state:
curl http://localhost:8080/api/v1/apps/engine_temp_sensor/x-medkit-topic-beacon
Example Response:
{
"entity_id": "engine_temp_sensor",
"status": "active",
"age_sec": 1.234,
"stable_id": "",
"display_name": "Engine Temperature Sensor",
"transport_type": "shared_memory",
"negotiated_format": "",
"process_id": 12345,
"process_name": "sensor_node",
"hostname": "robot-1",
"component_id": "powertrain",
"function_ids": ["monitoring"],
"depends_on": [],
"metadata": {"custom_key": "custom_value"}
}
Beacon Metadata Keys
When a beacon hint is applied to an entity, the following metadata keys are
injected into the entity’s response (e.g., GET /api/v1/apps/{id}).
These keys appear in the entity’s metadata field, NOT in the vendor
extension endpoint response.
The keys are built by build_metadata() in beacon_entity_mapper.cpp:
Key |
Type |
Description |
|---|---|---|
|
string |
Beacon state: |
|
number |
Seconds since the hint was last seen |
|
string |
DDS transport type (e.g., |
|
string |
Negotiated data format (e.g., |
|
integer |
OS process ID (PID) |
|
string |
Process name |
|
string |
Host identifier |
|
string |
Stable identity alias |
|
array |
Entity IDs this entity depends on |
|
array |
Function IDs this entity belongs to |
|
string |
Freeform metadata from |
Parameter Beacon Plugin (ParameterBeaconPlugin)
The ros2_medkit_param_beacon plugin enriches discovered entities with
pull-based metadata by polling ROS 2 node parameters. Unlike the topic beacon
which relies on nodes publishing to a shared topic, the parameter beacon
actively discovers and reads parameters matching a configurable prefix from
each known node.
This approach works well for nodes that already declare their metadata as
ROS 2 parameters (e.g., via declare_parameter()) without needing any
custom publishing code.
Configuration
ros2_medkit_gateway:
ros__parameters:
plugins: ["parameter_beacon"]
plugins.parameter_beacon.path: "/path/to/libparam_beacon_plugin.so"
# Parameter name prefix to scan
# Default: "ros2_medkit.discovery"
plugins.parameter_beacon.parameter_prefix: "ros2_medkit.discovery"
# How often to poll all nodes (seconds)
# Default: 5.0
plugins.parameter_beacon.poll_interval_sec: 5.0
# Maximum time per poll cycle (seconds)
# Default: 10.0
plugins.parameter_beacon.poll_budget_sec: 10.0
# Timeout for each node's parameter service call (seconds)
# Default: 2.0
plugins.parameter_beacon.param_timeout_sec: 2.0
# Soft TTL: hints older than this are marked STALE (seconds)
# Default: 15.0
plugins.parameter_beacon.beacon_ttl_sec: 15.0
# Hard expiry: hints older than this are removed (seconds)
# Default: 300.0
plugins.parameter_beacon.beacon_expiry_sec: 300.0
# Allow plugin to introduce entirely new entities not seen by other layers
# Default: false
plugins.parameter_beacon.allow_new_entities: false
# Maximum number of hints to keep in memory
# Default: 10000
plugins.parameter_beacon.max_hints: 10000
Parameter Naming
Nodes declare parameters under the configured prefix (default:
ros2_medkit.discovery). Each parameter maps to a BeaconHint field:
Parameter |
BeaconHint field |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example node (C++):
node->declare_parameter("ros2_medkit.discovery.entity_id", "my_sensor");
node->declare_parameter("ros2_medkit.discovery.display_name", "Temperature Sensor");
node->declare_parameter("ros2_medkit.discovery.function_ids",
std::vector<std::string>{"thermal_monitoring"});
node->declare_parameter("ros2_medkit.discovery.process_id",
static_cast<int64_t>(getpid()));
Running Both Plugins
The topic and parameter beacon plugins can be active simultaneously. Each
maintains its own BeaconHintStore and contributes independently to the
merge pipeline.
When both are active, align TTL and expiry values to avoid inconsistent staleness behavior:
ros2_medkit_gateway:
ros__parameters:
plugins: ["topic_beacon", "parameter_beacon"]
plugins.topic_beacon.path: "/path/to/libtopic_beacon_plugin.so"
plugins.topic_beacon.beacon_ttl_sec: 10.0
plugins.topic_beacon.beacon_expiry_sec: 300.0
plugins.parameter_beacon.path: "/path/to/libparam_beacon_plugin.so"
plugins.parameter_beacon.poll_interval_sec: 5.0
plugins.parameter_beacon.beacon_ttl_sec: 15.0
plugins.parameter_beacon.beacon_expiry_sec: 300.0
See Also
Manifest Schema Reference - Manifest-based configuration
Heuristic Runtime Discovery - Tutorial on runtime discovery
Manifest-Based Discovery - Hybrid mode tutorial
Plugin System - Plugin layer integration