Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
779c652
refactor(gateway): relocate operation result types into core/operations
bburda Apr 29, 2026
9e7ecca
refactor(gateway): relocate parameter result types into core/configur…
bburda Apr 29, 2026
532b947
refactor(gateway): relocate FaultResult into core/faults
bburda Apr 29, 2026
f3aa806
feat(gateway): introduce neutral Transport interfaces under core/tran…
bburda Apr 29, 2026
36a8bde
refactor(gateway): route DataAccessManager through TopicTransport ada…
bburda Apr 29, 2026
11cd08d
refactor(gateway): route OperationManager through Service/Action tran…
bburda Apr 29, 2026
9ad4485
refactor(gateway): route ConfigurationManager through ParameterTransport
bburda Apr 29, 2026
01bf7aa
refactor(gateway): route FaultManager through FaultServiceTransport
bburda Apr 29, 2026
67b11e6
refactor(gateway): route LogManager through LogSource adapter
bburda Apr 29, 2026
05b9af7
refactor(gateway): route TriggerManager through TopicSubscriptionTran…
bburda Apr 29, 2026
1e57616
refactor(gateway): convert runtime discovery to IntrospectionProvider
bburda Apr 29, 2026
fa3e40a
refactor: relocate TypeIntrospection to ros2_medkit_serialization
bburda Apr 29, 2026
22fc8f5
refactor(gateway): drive discovery refresh from rclcpp graph events
bburda Apr 29, 2026
2a06f9e
fix(gateway): only sweep orphan triggers on the backstop refresh
bburda Apr 29, 2026
f49c978
fix(opcua): bump test_opcua_plugin TIMEOUT to 240s
bburda Apr 29, 2026
14338d4
docs(gateway): refresh design index for managers-in-core layout
bburda Apr 29, 2026
902bc7a
fix(gateway): resolve clang-tidy findings on touched files
bburda Apr 29, 2026
9bae856
fix(gateway): pass goal by const-reference in goal-callback lambda
bburda Apr 29, 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
6 changes: 3 additions & 3 deletions scripts/check_no_naked_subscriptions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ ALLOWED_DIRS_PATTERN="(${GATEWAY_ROOT}/src/ros2_common/|${GATEWAY_ROOT}/include/
ALLOWED_LEGACY_FILES=(
"${GATEWAY_ROOT}/src/http/handlers/sse_fault_handler.cpp" # faults provider follow-up
"${GATEWAY_ROOT}/src/trigger_fault_subscriber.cpp" # faults provider follow-up
"${GATEWAY_ROOT}/src/trigger_topic_subscriber.cpp" # data_stream provider follow-up
"${GATEWAY_ROOT}/src/operation_manager.cpp" # operations provider follow-up
"${GATEWAY_ROOT}/src/log_manager.cpp" # logs provider follow-up
"${GATEWAY_ROOT}/src/ros2/trigger_topic_subscriber.cpp" # adapter for TopicSubscriptionTransport (rclcpp boundary)
"${GATEWAY_ROOT}/src/ros2/transports/ros2_action_transport.cpp" # operations provider follow-up
"${GATEWAY_ROOT}/src/ros2/transports/ros2_log_source.cpp" # /rosout adapter (LogSource port)
"${FAULT_MANAGER_ROOT}/src/snapshot_capture.cpp" # uses LockedSubscriptionGuard (in-place serialisation)
"${FAULT_MANAGER_ROOT}/include/ros2_medkit_fault_manager/snapshot_capture.hpp" # comment references the guarded API
"${FAULT_MANAGER_ROOT}/src/rosbag_capture.cpp" # bag-recorder spawns its own node + executor, no shared rcl hash map
Expand Down
128 changes: 117 additions & 11 deletions src/ros2_medkit_gateway/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,21 +149,26 @@ target_precompile_headers(gateway_core PRIVATE
# transitively get both layers from a single dependency.
add_library(gateway_ros2 STATIC
src/aggregation/aggregation_manager.cpp
src/configuration_manager.cpp
src/data_access_manager.cpp
src/data/ros2_topic_data_provider.cpp
src/ros2/conversions/fault_msg_conversions.cpp
src/ros2/providers/ros2_runtime_introspection.cpp
src/ros2/transports/ros2_action_transport.cpp
src/ros2/transports/ros2_fault_service_transport.cpp
src/ros2/transports/ros2_log_source.cpp
src/ros2/transports/ros2_parameter_transport.cpp
src/ros2/transports/ros2_service_transport.cpp
src/ros2/transports/ros2_topic_subscription_transport.cpp
src/ros2/transports/ros2_topic_transport.cpp
src/ros2/trigger_topic_subscriber.cpp
src/default_script_provider.cpp
src/discovery/discovery_manager.cpp
src/discovery/hybrid_discovery.cpp
src/discovery/layers/manifest_layer.cpp
src/discovery/layers/plugin_layer.cpp
src/discovery/layers/runtime_layer.cpp
src/discovery/manifest/manifest_manager.cpp
src/discovery/manifest/manifest_parser.cpp
src/discovery/manifest/runtime_linker.cpp
src/discovery/merge_pipeline.cpp
src/discovery/runtime_discovery.cpp
src/fault_manager.cpp
src/fault_manager_paths.cpp
src/gateway_node.cpp
src/http/handlers/auth_handlers.cpp
Expand All @@ -186,8 +191,6 @@ add_library(gateway_ros2 STATIC
src/http/handlers/update_handlers.cpp
src/http/http_server.cpp
src/http/rest_server.cpp
src/log_manager.cpp
src/operation_manager.cpp
src/openapi/capability_generator.cpp
src/openapi/openapi_spec_builder.cpp
src/openapi/path_builder.cpp
Expand All @@ -200,9 +203,6 @@ add_library(gateway_ros2 STATIC
src/ros2_common/ros2_subscription_slot.cpp
src/script_manager.cpp
src/trigger_fault_subscriber.cpp
src/trigger_manager.cpp
src/trigger_topic_subscriber.cpp
src/type_introspection.cpp
)

# Private implementation headers for openapi module (shared by both layers)
Expand Down Expand Up @@ -366,6 +366,112 @@ if(BUILD_TESTING)
)
set_tests_properties(gateway_core_smoke PROPERTIES LABELS "unit")

# ─── DataAccessManager routing test (gateway_core only) ────────────────────
# Mock-transport coverage proving the manager body compiles + links against
# gateway_core alone, with no rclcpp on the link line.
add_executable(test_data_access_manager_routing test/test_data_access_manager_routing.cpp)
target_link_libraries(test_data_access_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME data_access_manager_routing
COMMAND $<TARGET_FILE:test_data_access_manager_routing>
)
set_tests_properties(data_access_manager_routing PROPERTIES LABELS "unit")

# ─── OperationManager routing test (gateway_core only) ─────────────────────
# Mock-transport coverage proving the manager body (service / action call
# routing, goal tracking, status callback wiring, cleanup) compiles and
# links against gateway_core alone, with no rclcpp on the link line.
add_executable(test_operation_manager_routing test/test_operation_manager_routing.cpp)
target_link_libraries(test_operation_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME operation_manager_routing
COMMAND $<TARGET_FILE:test_operation_manager_routing>
)
set_tests_properties(operation_manager_routing PROPERTIES LABELS "unit")

# ─── ConfigurationManager routing test (gateway_core only) ─────────────────
# Mock-transport coverage proving the manager body (CRUD delegation,
# self-node short circuit, reset orchestration via cached defaults,
# shutdown idempotency, error code propagation) compiles and links against
# gateway_core alone, with no rclcpp on the link line.
add_executable(test_configuration_manager_routing test/test_configuration_manager_routing.cpp)
target_link_libraries(test_configuration_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME configuration_manager_routing
COMMAND $<TARGET_FILE:test_configuration_manager_routing>
)
set_tests_properties(configuration_manager_routing PROPERTIES LABELS "unit")

# ─── FaultManager routing test (gateway_core only) ─────────────────────────
# Mock-transport coverage proving the manager body (eight SOVD operations,
# error_message propagation, JSON-shape contract for get_fault_with_env)
# compiles and links against gateway_core alone, with no rclcpp on the link
# line.
add_executable(test_fault_manager_routing test/test_fault_manager_routing.cpp)
target_link_libraries(test_fault_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME fault_manager_routing
COMMAND $<TARGET_FILE:test_fault_manager_routing>
)
set_tests_properties(fault_manager_routing PROPERTIES LABELS "unit")

# ─── LogManager routing test (gateway_core only) ───────────────────────────
# Mock-source coverage proving the manager body (ring-buffer storage,
# plugin observer pattern, manages_ingestion short-circuit, severity
# filter, source teardown) compiles and links against gateway_core alone,
# with no rclcpp on the link line.
add_executable(test_log_manager_routing test/test_log_manager_routing.cpp)
target_link_libraries(test_log_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME log_manager_routing
COMMAND $<TARGET_FILE:test_log_manager_routing>
)
set_tests_properties(log_manager_routing PROPERTIES LABELS "unit")

# ─── TriggerManager routing test (gateway_core only) ───────────────────────
# Mock-transport coverage proving the manager body (data-trigger subscribe
# routing, handle-destruction unsubscribe, sample callback dispatch into
# the resource-change notifier, orphan sweep, multi-trigger isolation,
# shutdown teardown) compiles and links against gateway_core alone, with
# no rclcpp on the link line.
add_executable(test_trigger_manager_routing test/test_trigger_manager_routing.cpp)
target_link_libraries(test_trigger_manager_routing
PRIVATE
gateway_core
GTest::gtest
GTest::gtest_main
)
add_test(
NAME trigger_manager_routing
COMMAND $<TARGET_FILE:test_trigger_manager_routing>
)
set_tests_properties(trigger_manager_routing PROPERTIES LABELS "unit")

# Add GTest
find_package(ament_cmake_gtest REQUIRED)
find_package(rclcpp_action REQUIRED)
Expand Down Expand Up @@ -409,7 +515,7 @@ if(BUILD_TESTING)
medkit_target_dependencies(test_discovery_manager std_msgs)
medkit_set_test_domain(test_discovery_manager)

# Add RuntimeDiscoveryStrategy tests
# Add Ros2RuntimeIntrospection tests
ament_add_gtest(test_runtime_discovery test/test_runtime_discovery.cpp)
target_link_libraries(test_runtime_discovery gateway_ros2)
medkit_set_test_domain(test_runtime_discovery)
Expand Down
4 changes: 2 additions & 2 deletions src/ros2_medkit_gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ The gateway can be configured via parameters in `config/gateway_params.yaml` or
| ---------------------------- | ------ | ----------- | -------------------------------------------------------------------------------------- |
| `server.host` | string | `127.0.0.1` | Host to bind the REST server (`127.0.0.1` for localhost, `0.0.0.0` for all interfaces) |
| `server.port` | int | `8080` | Port for the REST API (range: 1024-65535) |
| `refresh_interval_ms` | int | `10000` | Cache refresh interval in milliseconds (range: 100-60000) |
| `refresh_interval_ms` | int | `30000` | Safety-backstop refresh interval in milliseconds. Primary refresh is graph-event driven; this controls only the periodic forced refresh (range: 100-60000) |
| `data_provider.max_parallel_samples` | int | `8` | Max concurrent topic samples when fetching data (range: 1-256) |
| `topic_sample_timeout_sec` | float | `2.0` | Timeout for sampling topics with active publishers (range: 0.1-30.0) |
| `fault_manager.namespace` | string | `""` | Optional namespace prefix for fault manager services and events (for example `robot1`) |
Expand Down Expand Up @@ -1405,7 +1405,7 @@ ros2 launch ros2_medkit_gateway gateway_https.launch.py min_tls_version:=1.3
| `server_host` | `127.0.0.1` | Host to bind HTTPS server |
| `server_port` | `8443` | Port for HTTPS API |
| `min_tls_version` | `1.2` | Minimum TLS version (`1.2` or `1.3`) |
| `refresh_interval_ms` | `10000` | Cache refresh interval in milliseconds |
| `refresh_interval_ms` | `30000` | Safety-backstop refresh interval (graph events drive primary refresh) |

**Usage with curl (self-signed certs):**
```bash
Expand Down
27 changes: 19 additions & 8 deletions src/ros2_medkit_gateway/config/gateway_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ ros2_medkit_gateway:
# TODO: Mutual TLS (client certificate verification) is not yet implemented
# See: https://github.com/selfpatch/ros2_medkit/issues/XXX

# Cache refresh interval in milliseconds
# How often to discover ROS 2 nodes and update the cache
# Safety-backstop refresh interval in milliseconds.
#
# Primary discovery is driven by rclcpp graph events (polled every
# 100 ms): a refresh runs whenever a node, topic, service or action
# is added or removed, so on a stable graph this timer rarely fires.
# This value controls only the safety backstop that forces a refresh
# in the unlikely case a graph event is missed (lost wakeup, rclcpp
# anomaly, etc.).
#
# Valid range: 100-60000 (0.1s to 60s)
# Default: 10000 (10 seconds) - optimized for developer experience
refresh_interval_ms: 10000
# Default: 30000 (30 seconds)
refresh_interval_ms: 30000

# CORS Configuration
# Cross-Origin Resource Sharing settings for browser-based clients
Expand Down Expand Up @@ -156,10 +163,14 @@ ros2_medkit_gateway:
# Controls how ROS 2 graph entities are mapped to SOVD entities
discovery:
# Discovery mode
# Options:
# - "runtime_only": Use ROS 2 graph introspection only (default)
# - "manifest_only": Only expose manifest-declared entities
# - "hybrid": Manifest as source of truth + runtime linking
# Selects which layers the merge pipeline activates:
# - "runtime_only": Bypass the pipeline; ROS 2 graph introspection
# drives entities directly (default).
# - "manifest_only": Bypass the pipeline; manifest is the sole source
# of truth.
# - "hybrid": Run the merge pipeline with manifest + runtime + plugin
# layers. Manifest is the source of truth; runtime data links to
# manifest apps.
mode: "runtime_only"

# Path to manifest file (required for manifest_only and hybrid modes)
Expand Down
9 changes: 5 additions & 4 deletions src/ros2_medkit_gateway/design/aggregation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,11 @@ Health Monitoring
~~~~~~~~~~~~~~~~~

``AggregationManager`` calls ``check_all_health()`` during each entity cache
refresh cycle (controlled by ``refresh_interval_ms``, default: 10000 ms). Each
``PeerClient`` GETs ``/api/v1/health`` on its peer. If the health check fails,
the peer is marked unhealthy and excluded from fan-out queries and entity
fetching.
refresh cycle. Refresh is primarily driven by rclcpp graph events (polled
every 100 ms), with ``refresh_interval_ms`` (default: 30000 ms) providing a
safety backstop for the case a graph event is missed. Each ``PeerClient``
GETs ``/api/v1/health`` on its peer. If the health check fails, the peer is
marked unhealthy and excluded from fan-out queries and entity fetching.

When a peer recovers (health check succeeds again), it is automatically
re-included.
Expand Down
Loading