From 9d5ad1dc5faea49605fcdc4de749b2f550f3a61b Mon Sep 17 00:00:00 2001 From: Michal Faferek Date: Sat, 20 Jun 2026 21:07:54 +0200 Subject: [PATCH 1/2] docs(tutorials): add attach-to-existing-stack guide How to point the gateway at a running stack you did not launch: matching RMW / domain / distro, discovery reachability, containers, web UI CORS, and a troubleshooting table. Closes #448 --- docs/tutorials/attach-existing-stack.rst | 135 +++++++++++++++++++++++ docs/tutorials/index.rst | 5 + 2 files changed, 140 insertions(+) create mode 100644 docs/tutorials/attach-existing-stack.rst diff --git a/docs/tutorials/attach-existing-stack.rst b/docs/tutorials/attach-existing-stack.rst new file mode 100644 index 00000000..4e9f8ceb --- /dev/null +++ b/docs/tutorials/attach-existing-stack.rst @@ -0,0 +1,135 @@ +Attach to an Existing ROS 2 Stack +================================= + +ros2_medkit discovers a running ROS 2 graph at runtime and builds the SOVD +entity tree with no manifest and no changes to your nodes. This guide covers +the common case of pointing the gateway at a stack you did not launch yourself +(for example Nav2, MoveIt, or Autoware), including the cross-process ROS 2 +setup that any external node needs. + +.. contents:: Table of Contents + :local: + :depth: 2 + +The short version +----------------- + +If you install ros2_medkit from a binary release into the **same ROS 2 +environment** as your stack, it already shares the RMW, domain and distro. +Source it and run the gateway; it discovers the live graph: + +.. code-block:: bash + + source /opt/ros/jazzy/setup.bash + ros2 run ros2_medkit_gateway gateway_node + # SOVD entity tree + faults at http://localhost:8080/api/v1/ + +Everything below is for the case where medkit runs in a different environment +than your stack (a separate container, image, or install). + +Match the ROS 2 environment +--------------------------- + +medkit talks to your stack over DDS like any other ROS 2 process. Four things +must line up, or the gateway sees an empty graph: + +- **Distro.** ROS 2 distros do not interoperate over DDS. Run the gateway on + the same distro as the target stack (Humble with Humble, Jazzy with Jazzy). +- **RMW implementation.** Both sides must use the same RMW. If your stack runs + CycloneDDS, set it for the gateway too: + + .. code-block:: bash + + export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + + The prebuilt image bundles both FastDDS (default) and CycloneDDS; opt in with + the environment variable above. +- **Domain ID.** ``ROS_DOMAIN_ID`` must match the stack (default is 0). +- **Discovery reachability.** The processes must reach each other's DDS + discovery: same host, a shared network, or - for containers - a shared + network namespace (see :ref:`attach-containers`). + +Run the gateway against the graph +--------------------------------- + +Runtime discovery is the default; no manifest is required. The gateway builds +the SOVD tree from whatever is on the graph: + +.. code-block:: bash + + ros2 run ros2_medkit_gateway gateway_node + # nodes -> SOVD Apps + curl -s http://localhost:8080/api/v1/apps | jq '.items | length' + # whole-system faults (from /diagnostics, /rosout, aborted actions via the bridges) + curl -s http://localhost:8080/api/v1/faults | jq + +.. _attach-containers: + +Containers +---------- + +When the gateway runs as a container attaching to a stack on the host, give it +access to the host's DDS traffic and match the environment: + +.. code-block:: bash + + docker run --rm \ + --network host \ + -e ROS_DOMAIN_ID=0 \ + -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \ + -p 8080:8080 \ + ghcr.io/selfpatch/ros2_medkit:latest + +If the stack itself runs in a container, share its network namespace instead of +the host network: + +.. code-block:: bash + + docker run --rm --network container: \ + -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ghcr.io/selfpatch/ros2_medkit:latest + +.. note:: + + On hosts with a small ``net.core.rmem_max``, CycloneDDS may warn about the + socket receive buffer. Basic operation is unaffected; if a large-throughput + stack needs it, raise ``net.core.rmem_max`` or point ``CYCLONEDDS_URI`` at a + tuned ``cyclonedds.xml``. + +Web UI +------ + +The web UI is a separate service served from its own origin, so the gateway +must allow that origin via CORS. The Docker image enables a permissive CORS +default for this; for a manual gateway set ``cors.allowed_origins`` to your UI +origin(s). See :doc:`web-ui` and :doc:`docker`. Restrict origins and enable +authentication (:doc:`authentication`) for production. + +Verify +------ + +.. code-block:: bash + + curl -s http://localhost:8080/api/v1/health # -> healthy + curl -s http://localhost:8080/api/v1/apps | jq '.items | length' # your nodes + curl -s http://localhost:8080/api/v1/faults | jq '."x-medkit".count' + +Troubleshooting +--------------- + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Symptom + - Likely cause + * - Empty entity tree / no apps discovered + - RMW, ``ROS_DOMAIN_ID`` or distro mismatch, or discovery not reachable + (wrong network / namespace). + * - Gateway loads but the stack uses CycloneDDS + - ``RMW_IMPLEMENTATION`` not set to ``rmw_cyclonedds_cpp`` (the image + default is FastDDS). + * - Web UI shows "Failed to fetch" + - CORS: the gateway is not allowing the UI's origin. + * - Cannot reach the gateway from another host + - Bind host is loopback; set ``server.host`` to ``0.0.0.0`` and expose the + port. diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst index d7029c41..38a343a4 100644 --- a/docs/tutorials/index.rst +++ b/docs/tutorials/index.rst @@ -20,6 +20,7 @@ Step-by-step guides for common use cases with ros2_medkit. docker devcontainer integration + attach-existing-stack custom_areas web-ui mcp-server @@ -107,6 +108,10 @@ Advanced Tutorials :doc:`integration` Integrate ros2_medkit with your existing ROS 2 system. +:doc:`attach-existing-stack` + Point the gateway at a running stack you did not launch (matching RMW, + domain and distro), including container and web UI setup. + :doc:`custom_areas` Customize the entity hierarchy for your robot architecture. From 6554d1b38fd395cc1661a7b3c5675d054d55d62e Mon Sep 17 00:00:00 2001 From: Michal Faferek Date: Sun, 21 Jun 2026 11:35:15 +0200 Subject: [PATCH 2/2] docs(tutorials): address review on attach-existing-stack - RMW wording: do not imply the stock image already bundles CycloneDDS; say the selected RMW must be installed. - Container examples: drop the no-op -p with --network host, use the distro-specific image tag, add ROS_DOMAIN_ID to both. - Web UI: point to setting cors.allowed_origins rather than asserting a permissive image default. - Verify: use .items | length for the faults count. --- docs/tutorials/attach-existing-stack.rst | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/tutorials/attach-existing-stack.rst b/docs/tutorials/attach-existing-stack.rst index 4e9f8ceb..662d2f08 100644 --- a/docs/tutorials/attach-existing-stack.rst +++ b/docs/tutorials/attach-existing-stack.rst @@ -42,8 +42,9 @@ must line up, or the gateway sees an empty graph: export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp - The prebuilt image bundles both FastDDS (default) and CycloneDDS; opt in with - the environment variable above. + The RMW you select must be installed in the gateway's environment. A binary + install pulls in the default RMW; install the matching ``ros--rmw-*`` + package for any other implementation. - **Domain ID.** ``ROS_DOMAIN_ID`` must match the stack (default is 0). - **Discovery reachability.** The processes must reach each other's DDS discovery: same host, a shared network, or - for containers - a shared @@ -73,12 +74,13 @@ access to the host's DDS traffic and match the environment: .. code-block:: bash + # With --network host the gateway shares the host's ports directly + # (no -p needed); reach the API at http://localhost:8080. docker run --rm \ --network host \ -e ROS_DOMAIN_ID=0 \ -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \ - -p 8080:8080 \ - ghcr.io/selfpatch/ros2_medkit:latest + ghcr.io/selfpatch/ros2_medkit-jazzy:latest If the stack itself runs in a container, share its network namespace instead of the host network: @@ -86,7 +88,9 @@ the host network: .. code-block:: bash docker run --rm --network container: \ - -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ghcr.io/selfpatch/ros2_medkit:latest + -e ROS_DOMAIN_ID=0 \ + -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \ + ghcr.io/selfpatch/ros2_medkit-jazzy:latest .. note:: @@ -99,10 +103,9 @@ Web UI ------ The web UI is a separate service served from its own origin, so the gateway -must allow that origin via CORS. The Docker image enables a permissive CORS -default for this; for a manual gateway set ``cors.allowed_origins`` to your UI -origin(s). See :doc:`web-ui` and :doc:`docker`. Restrict origins and enable -authentication (:doc:`authentication`) for production. +must allow that origin via CORS. Set ``cors.allowed_origins`` to your UI +origin(s) in the gateway params. See :doc:`web-ui` and :doc:`docker`. Restrict +origins and enable authentication (:doc:`authentication`) for production. Verify ------ @@ -111,7 +114,7 @@ Verify curl -s http://localhost:8080/api/v1/health # -> healthy curl -s http://localhost:8080/api/v1/apps | jq '.items | length' # your nodes - curl -s http://localhost:8080/api/v1/faults | jq '."x-medkit".count' + curl -s http://localhost:8080/api/v1/faults | jq '.items | length' # active faults Troubleshooting ---------------