Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0bc2c55
refactor: replace dynamic_cast with virtual filtered_count() in merge…
bburda Mar 25, 2026
59d8d9b
docs: document thread-safety constraints on MergePipeline accessors
bburda Mar 25, 2026
85e83fb
fix: move external field from STATUS to METADATA field group
bburda Mar 25, 2026
abe3a3e
perf: execute merge pipeline outside mutex in HybridDiscovery
bburda Mar 25, 2026
1282bfa
fix: suppress runtime duplicate apps after linking
bburda Mar 25, 2026
78f8d6a
fix: suppress runtime duplicate components and areas after linking
bburda Mar 25, 2026
d1327e9
feat: add flat robot manifest example without areas
bburda Mar 25, 2026
1192111
feat: add create_synthetic_areas runtime discovery parameter
bburda Mar 25, 2026
1ae227e
feat: add node-to-entity resolver to TriggerFaultSubscriber
bburda Mar 25, 2026
58a0aca
feat: wire node-to-entity resolver for log and fault triggers
bburda Mar 25, 2026
aa565e8
test: add merge pipeline coverage for areas, merge_bool, validation
bburda Mar 25, 2026
f796251
style: fix clang-tidy warnings in test_health_handlers
bburda Mar 25, 2026
934372b
test: add integration test for flat entity tree without areas
bburda Mar 25, 2026
c90b13a
test: add hybrid suppression integration test
bburda Mar 25, 2026
94fe41c
docs: document flat entity tree and create_synthetic_areas parameter
bburda Mar 25, 2026
8eb2b58
fix: resolve deadlock in stop_rest_server and ROS_DOMAIN_ID collision
bburda Mar 25, 2026
cc980b4
fix: root-namespace suppression, atomic cache update, external merge …
bburda Mar 25, 2026
d171281
test: fix weak assertions and add missing unit tests
bburda Mar 25, 2026
ba84ea9
style: fix clang-tidy performance warnings in test_fault_manager
bburda Mar 25, 2026
0f7114c
test: add gap-fill positive assertion for manifest apps
bburda Mar 25, 2026
4bc4c86
fix: address round 2 review findings
bburda Mar 25, 2026
3ef8740
fix: decouple runtime apps for linking from gap-fill filtering
bburda Mar 25, 2026
8a51c66
test: update hybrid mode tests to use manifest entity IDs after #307
bburda Mar 25, 2026
851d373
fix: address PR review feedback
bburda Mar 25, 2026
46eeffc
docs: add @param for node_to_app in update_all docstring
bburda Mar 25, 2026
5bfec95
fix: remove trailing underscores from test comment breaking RST
bburda Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/config/discovery-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,40 @@ When ``create_synthetic_components`` is true:
- ``grouping_strategy: "namespace"`` groups nodes by their first namespace segment
- ``synthetic_component_name_pattern`` defines 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
^^^^^^^^^^^^^^^

.. code-block:: yaml

discovery:
runtime:
create_synthetic_areas: true

When ``create_synthetic_areas`` is true (the default):

- Areas are created from ROS 2 namespace segments (e.g., ``/powertrain`` becomes area ``powertrain``)
- 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

.. code-block:: yaml

# Flat entity tree - no areas
discovery:
runtime:
create_synthetic_areas: false

Topic-Only Namespaces
^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -91,6 +125,11 @@ 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)
-----------------------------

Expand Down Expand Up @@ -139,7 +178,7 @@ Field Groups
* - ``status``
- is_online, bound_fqn
* - ``metadata``
- source, x-medkit extensions, custom metadata fields
- source, ros_binding, external, x-medkit extensions, custom metadata fields

Merge Policies
^^^^^^^^^^^^^^
Expand Down Expand Up @@ -274,6 +313,9 @@ Complete YAML configuration for runtime 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"
Expand Down
64 changes: 64 additions & 0 deletions docs/config/manifest-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ Areas
Areas represent logical or physical groupings (subsystems, locations, etc.).
In runtime-only mode, areas are derived from ROS 2 namespaces.

.. note::

The ``areas:`` section is optional. For simple robots without subsystem hierarchy,
you can omit areas entirely and use components as the top-level entities. See
:ref:`manifest-flat-entity-tree` below.

Schema
~~~~~~

Expand Down Expand Up @@ -727,6 +733,64 @@ Example

See the Scripts section in :doc:`/api/rest` for API endpoints.

.. _manifest-flat-entity-tree:

Flat Entity Tree
----------------

For simple robots where the entire system is a single unit, you can omit the
``areas:`` section and use a flat component tree instead. The top-level component
represents the robot itself, with subcomponents for hardware modules:

.. code-block:: yaml

manifest_version: "1.0"

metadata:
name: "flat-turtlebot"
version: "1.0.0"
description: "TurtleBot3 without area hierarchy"

# No areas section - components are top-level entities

components:
- id: turtlebot3
name: "TurtleBot3 Burger"
type: "mobile-robot"

- id: raspberry-pi
name: "Raspberry Pi 4"
type: "controller"
parent_component_id: turtlebot3

- id: lds-sensor
name: "LDS-02 LiDAR"
type: "sensor"
parent_component_id: turtlebot3

apps:
- id: lidar-driver
name: "LiDAR Driver"
is_located_on: lds-sensor
ros_binding:
node_name: ld08_driver
namespace: /

For manifest-based discovery (``manifest_only`` or ``hybrid``), simply omit the
``areas:`` section as shown above - no additional configuration is needed.
For runtime-only discovery, set ``create_synthetic_areas: false`` in
``gateway_params.yaml`` to prevent automatic area creation from namespaces (see
:doc:`discovery-options`). A complete example is available at
``config/examples/flat_robot_manifest.yaml`` in the gateway package.

.. note::

In hybrid mode, the runtime gap-fill layer may create synthetic areas
from namespaces even when the manifest omits ``areas:``. Set
``merge_pipeline.gap_fill.allow_heuristic_areas: false`` to prevent
this and maintain a fully flat tree. See :doc:`discovery-options`
for gap-fill configuration details.

Complete Example
----------------

Expand Down
12 changes: 8 additions & 4 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ Step 3: Discover Areas and Components

ros2_medkit organizes ROS 2 nodes into a SOVD-aligned entity hierarchy:

- **Areas** Logical/physical domains (e.g., ``/powertrain``, ``/chassis``)
- **Components** Hardware or virtual units that group Apps
- **Apps** Individual ROS 2 nodes
- **Functions** Cross-cutting capabilities (requires manifest mode)
- **Areas** - Logical/physical domains (e.g., ``/powertrain``, ``/chassis``)
- **Components** - Hardware or virtual units that group Apps
- **Apps** - Individual ROS 2 nodes
- **Functions** - Cross-cutting capabilities (requires manifest mode)

.. note::

Expand All @@ -178,6 +178,10 @@ ros2_medkit organizes ROS 2 nodes into a SOVD-aligned entity hierarchy:
links them to live ROS 2 nodes.
- **Manifest-only**: Only manifest-declared entities are exposed.

Areas are optional. Simple robots can use a flat component tree without
areas by setting ``discovery.runtime.create_synthetic_areas: false`` or
by omitting the ``areas:`` section in the manifest.

See :doc:`tutorials/manifest-discovery` for details on manifest mode.

In this tutorial, we use runtime-only mode with ``demo_nodes.launch.py``.
Expand Down
89 changes: 81 additions & 8 deletions docs/tutorials/manifest-discovery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,19 @@ Runtime Linking
field-group before linking. See :doc:`/config/discovery-options` for merge
pipeline configuration.

.. note::

**Trigger entity resolution in hybrid mode**

In hybrid mode, fault and log triggers are delivered using the manifest entity
ID rather than the raw ROS node FQN. The runtime linker builds a node-to-app
mapping during the linking phase (step 4 above), and the fault manager and log
manager use this mapping to resolve incoming trigger events to the correct
manifest entity. This means fault codes, log subscriptions, and SSE events
reference stable manifest IDs (e.g., ``lidar-driver``) instead of the
ephemeral node FQN (e.g., ``/sensors/velodyne_driver``), even when nodes
restart and re-bind.

ROS Binding Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -332,19 +345,25 @@ Example response:
Entity Hierarchy
----------------

The manifest defines a hierarchical structure:
The manifest supports two hierarchy patterns depending on your robot's complexity.

With Areas (Complex Robots)
~~~~~~~~~~~~~~~~~~~~~~~~~~~

For robots with multiple subsystems (e.g., autonomous vehicles, industrial platforms),
use areas to group components by function or location:

.. code-block:: text

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

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

**Areas** group related components by function or location:

Expand All @@ -357,7 +376,7 @@ The manifest defines a hierarchical structure:
- id: lidar-processing
name: "LiDAR Processing"

**Components** represent hardware or virtual units:
**Components** are assigned to areas:

.. code-block:: yaml

Expand All @@ -370,6 +389,60 @@ The manifest defines a hierarchical structure:
- id: gpu-unit
name: "GPU Processing Unit"

Without Areas (Simple Robots)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For simple robots where the entire system is a single unit (e.g., TurtleBot3, small
mobile platforms), you can omit areas entirely and use a flat component tree. The
robot itself is the top-level component, with subcomponents for hardware modules:

.. code-block:: text

Components (top-level)
+-- Subcomponents (hardware modules)
+-- Apps (software applications)

Functions (cross-cutting capabilities)
+-- Apps (hosted by)

.. code-block:: yaml

# No areas section needed
components:
- id: turtlebot3
name: "TurtleBot3 Burger"
type: "mobile-robot"

- id: raspberry-pi
name: "Raspberry Pi 4"
type: "controller"
parent_component_id: turtlebot3

apps:
- id: nav2-controller
name: "Nav2 Controller"
is_located_on: raspberry-pi
ros_binding:
node_name: controller_server
namespace: /

For runtime-only mode, set ``discovery.runtime.create_synthetic_areas: false``
to prevent automatic area creation from namespaces. See
:ref:`manifest-flat-entity-tree` in the manifest schema reference and
``config/examples/flat_robot_manifest.yaml`` for a complete example.

**When to use each pattern:**

- **With areas**: Multiple subsystems, deep namespace hierarchy, large teams
working on separate domains (perception, navigation, control)
- **Without areas**: Single robot with a handful of nodes, flat or shallow
namespace structure, quick prototypes

Common Elements
~~~~~~~~~~~~~~~

Both patterns use **Apps** and **Functions** the same way.

**Apps** are software applications (typically ROS 2 nodes):

.. code-block:: yaml
Expand Down
4 changes: 3 additions & 1 deletion src/ros2_medkit_gateway/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ if(BUILD_TESTING)
ament_add_gtest(test_fault_manager test/test_fault_manager.cpp)
target_link_libraries(test_fault_manager gateway_lib)
medkit_target_dependencies(test_fault_manager rclcpp ros2_medkit_msgs)
set_tests_properties(test_fault_manager PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=99")

# Add discovery models tests
ament_add_gtest(test_discovery_models test/test_discovery_models.cpp)
Expand Down Expand Up @@ -475,7 +476,7 @@ if(BUILD_TESTING)
# Add script handler tests
ament_add_gtest(test_script_handlers test/test_script_handlers.cpp)
target_link_libraries(test_script_handlers gateway_lib)
set_tests_properties(test_script_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=69")
set_tests_properties(test_script_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=80")

# Add data handler tests
ament_add_gtest(test_data_handlers test/test_data_handlers.cpp)
Expand All @@ -488,6 +489,7 @@ if(BUILD_TESTING)
# Add health handler tests
ament_add_gtest(test_health_handlers test/test_health_handlers.cpp)
target_link_libraries(test_health_handlers gateway_lib)
set_tests_properties(test_health_handlers PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=79")

# Add operation handler tests
ament_add_gtest(test_operation_handlers test/test_operation_handlers.cpp)
Expand Down
Loading
Loading