ros2_medkit_gateway
This section contains design documentation for the ros2_medkit_gateway project.
Architecture
The following diagram shows the relationships between the main components of the gateway.
ROS 2 Medkit Gateway Class Architecture
Main Components
GatewayNode - The main ROS 2 node that orchestrates the system - Extends
rclcpp::Node- Manages periodic discovery and cache refresh - Runs the REST server in a separate thread - Provides thread-safe access to the entity cache - Manages periodic cleanup of old action goals (60s interval)DiscoveryManager - Discovers ROS 2 entities and maps them to the SOVD hierarchy - Discovers Areas from node namespaces or manifest definitions - Discovers Components (synthetic groups from runtime, or explicit from manifest) - Discovers Apps from ROS 2 nodes (individual running processes) - Discovers Services and Actions using native rclcpp APIs - Attaches operations (services/actions) to their parent Apps and Components - Uses pluggable strategy pattern: Runtime, Manifest, or Hybrid - Uses O(n+m) algorithm with hash maps for efficient service/action attachment
Discovery Strategies:
RuntimeDiscoveryStrategy - Heuristic discovery via ROS 2 graph introspection - Maps nodes to Apps with
source: "heuristic"- Creates synthetic Components grouped by namespace - Handles topic-only namespaces (Isaac Sim, bridges) via TopicOnlyPolicyManifestDiscoveryStrategy - Static discovery from YAML manifest - Provides stable, semantic entity IDs - Supports offline detection of failed components
HybridDiscoveryStrategy - Combines manifest + runtime via MergePipeline - Delegates to
MergePipelinewhich orchestrates ordered discovery layers - Supports dynamic plugin layers added at runtime - Thread-safe: mutex protects cached results, returns by value
Merge Pipeline:
The
MergePipelineis the core engine for hybrid discovery. It:Maintains an ordered list of
DiscoveryLayerinstances (first = highest priority)Executes all layers, collects entities by ID, and merges them per-field-group
Each layer declares a
MergePolicyperFieldGroup: AUTHORITATIVE (wins), ENRICHMENT (fills empty), FALLBACK (last resort)Runs
RuntimeLinkerpost-merge to bind manifest apps to live ROS 2 nodesProduces a
MergeReportwith conflict diagnostics, enrichment counts, and ID collision detection
Built-in Layers:
ManifestLayer- Wraps ManifestManager; IDENTITY/HIERARCHY/METADATA are AUTHORITATIVE, LIVE_DATA is ENRICHMENT (runtime wins for topics/services), STATUS is FALLBACKRuntimeLayer- Wraps RuntimeDiscoveryStrategy; LIVE_DATA/STATUS are AUTHORITATIVE, METADATA is ENRICHMENT, IDENTITY/HIERARCHY are FALLBACK. SupportsGapFillConfigto control which heuristic entities are allowed when manifest is presentPluginLayer- Wraps IntrospectionProvider; all fields ENRICHMENT (plugins enrich, they don’t override). Before each layer’sdiscover()call, the pipeline populatesIntrospectionInputwith entities from all previous layers, so plugins see the current manifest + runtime entity set
OperationManager - Executes ROS 2 operations (services and actions) using native APIs - Calls ROS 2 services via
rclcpp::GenericClientwith native serialization - Sends action goals via native action client interfaces - Tracks active action goals with status, feedback, and timestamps - Subscribes to/_action/statustopics for real-time goal status updates - Supports goal cancellation via native cancel service calls - Supports SOVD capability-based control (stop maps to ROS 2 cancel) - Automatically cleans up completed goals older than 5 minutes - Usesros2_medkit_serializationfor JSON ↔ ROS 2 message conversionRESTServer - Provides the HTTP/REST API - Discovery endpoints:
/health,/areas,/components- Data endpoints:/components/{id}/data,/components/{id}/data/{topic}- Operations endpoints:/apps/{id}/operations,/apps/{id}/operations/{op}/executions- Configurations endpoints:/apps/{id}/configurations,/apps/{id}/configurations/{param}- Scripts endpoints:/{entity_type}/{id}/scripts,/{entity_type}/{id}/scripts/{script_id}/executions- Retrieves cached entities from the GatewayNode - Uses DataAccessManager for runtime topic data access - Uses OperationManager for service/action execution - Uses ConfigurationManager for parameter CRUD operations - Uses ScriptManager for script upload and execution - Runs on configurable host and port with CORS supportConfigurationManager - Manages ROS 2 node parameters - Lists all parameters for a node via
rclcpp::SyncParametersClient- Gets/sets individual parameter values with type conversion - Provides parameter descriptors (description, constraints, read-only flag) - Caches parameter clients per node for efficiency - Converts between JSON and ROS 2 parameter types automaticallyDataAccessManager - Reads and writes runtime data from/to ROS 2 topics - Uses native rclcpp APIs for fast topic discovery and sampling - Checks publisher counts before sampling to skip idle topics instantly - Returns metadata (type, schema) for topics without publishers - Uses native
rclcpp::GenericPublisherfor topic publishing with CDR serialization - Returns topic data as JSON with metadata (topic name, timestamp, type info) - Parallel topic sampling with configurable concurrency limit (max_parallel_topic_samples, default: 10)NativeTopicSampler - Fast topic sampling using native rclcpp APIs - Discovers topics via
node->get_topic_names_and_types()- Usesrclcpp::GenericSubscriptionfor type-agnostic message sampling - Checkscount_publishers()before sampling to skip idle topics - Returns metadata instantly for topics without publishers (no timeout) - Significantly improves UX when robot has many idle topicsJsonSerializer (ros2_medkit_serialization) - Converts between JSON and ROS 2 messages - Uses
dynmsglibrary for dynamic type introspection - Serializes JSON to CDR format for publishing viaserialize()- Deserializes CDR to JSON for subscriptions viadeserialize()- Converts between deserialized ROS 2 messages and JSON viato_json()/from_json()- Provides staticyaml_to_json()utility for YAML to JSON conversion - Thread-safe and stateless design- LockManager - SOVD resource locking (ISO 17978-3, Section 7.17)
Transport-agnostic lock store with
shared_mutexfor thread safetyAcquire, release, extend locks on components and apps
Scoped locks restrict protection to specific resource collections
Lazy parent propagation (lock on component protects child apps)
Lock-required enforcement via per-entity
required_scopesconfigAutomatic expiry with cyclic subscription cleanup
Plugin access via
PluginContext::check_lock/acquire_lock/release_lock
Data Models - Entity representations -
Area- Physical or logical domain (namespace grouping) -Component- Logical grouping of Apps; can besynthetic(auto-created from namespace),topic(from topic-only namespace), ormanifest(explicitly defined) -App- Software application (ROS 2 node); individual running process linked to parent Component -ServiceInfo- Service metadata (path, name, type) -ActionInfo- Action metadata (path, name, type) -EntityCache- Thread-safe cache of discovered entities (areas, components, apps)
ScriptManager - Manages diagnostic script upload, storage, and execution (SOVD 7.15) - Delegates to a pluggable
ScriptProviderbackend (set viaset_backend()) - Lists, uploads, and deletes scripts per entity - Starts script executions as POSIX subprocesses with timeout support - Tracks execution status (prepared,running,completed,failed,terminated) - Supports termination viastop(SIGTERM) andforced_termination(SIGKILL) - Built-inDefaultScriptProviderhandles filesystem storage and manifest-defined scripts - Supports concurrent execution limits and per-script timeout configuration
Triggers
The trigger subsystem implements SOVD condition-based resource change notifications. It consists of five main components:
TriggerManager - Central coordinator for trigger lifecycle (CRUD), condition evaluation, and event dispatch.
Subscribes to
ResourceChangeNotifierfor resource change eventsEvaluates conditions using registered
ConditionEvaluatorinstances via theConditionRegistryUses O(1) dispatch indexing by
{collection, entity_id}for efficient notification matchingSupports entity hierarchy matching (area-level triggers catch descendant changes)
Manages pending events for SSE stream pickup with per-trigger mutexes
Persists triggers via the
TriggerStoreinterface
ResourceChangeNotifier - Async notification hub for resource changes.
Producers (FaultManager, DataAccessManager, UpdateManager, OperationManager, LogManager) call
notify()Observers (TriggerManager) register callbacks with filters
notify()is non-blocking - pushes to an internal queue processed by a dedicated worker threadFilters support collection, entity_id, and resource_path matching
ConditionRegistry - Thread-safe registry for condition evaluators.
Built-in SOVD types:
OnChange,OnChangeTo,EnterRange,LeaveRangePlugins register custom evaluators with
x-prefixed namesUses
shared_mutexfor concurrent read access during evaluation
TriggerStore / SqliteTriggerStore - Persistence backend for triggers.
Abstract
TriggerStoreinterface allows plugin-provided backendsDefault
SqliteTriggerStoreuses SQLite for persistent triggersStores trigger metadata, condition parameters, and evaluator state (previous values)
Supports partial updates for status changes and lifetime extensions
TriggerTopicSubscriber - Manages ROS 2 topic subscriptions for data triggers.
Creates
rclcpp::GenericSubscriptioninstances for monitored data topicsReference-counted: multiple triggers on the same topic share one subscription
Publishes data changes to
ResourceChangeNotifierfor condition evaluationAutomatically unsubscribes when the last trigger for a topic is removed