From 61a47cbbad6fe7064526d3540092f8699974760f Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 26 May 2026 16:53:56 +0200 Subject: [PATCH 1/6] fix(demos/lib): resolve sourced lib path via BASH_SOURCE instead of $0 The per-demo wrappers source lib/setup-trigger.sh and lib/watch-trigger.sh from inside demos//. Both sourced files then re-source triggers-api.sh with: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" When sourced, $0 stays the caller's script path - the demo wrapper - not the lib file's path. So SCRIPT_DIR resolves to demos// and the second source fails with: demos//triggers-api.sh: No such file or directory ${BASH_SOURCE[0]} is the path of the file currently executing the line, which is what we actually want. --- lib/setup-trigger.sh | 2 +- lib/watch-trigger.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/setup-trigger.sh b/lib/setup-trigger.sh index 83c937a..e43f5e2 100644 --- a/lib/setup-trigger.sh +++ b/lib/setup-trigger.sh @@ -8,7 +8,7 @@ if [ -z "${ENTITY_TYPE:-}" ] || [ -z "${ENTITY_ID:-}" ]; then exit 1 fi -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/triggers-api.sh" diff --git a/lib/watch-trigger.sh b/lib/watch-trigger.sh index 57dfc1a..c16db14 100644 --- a/lib/watch-trigger.sh +++ b/lib/watch-trigger.sh @@ -8,7 +8,7 @@ if [ -z "${ENTITY_TYPE:-}" ] || [ -z "${ENTITY_ID:-}" ]; then exit 1 fi -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/triggers-api.sh" From 1ba654a6c2261a518a46e3b59f811d5ce407fd2d Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 26 May 2026 16:54:40 +0200 Subject: [PATCH 2/6] fix(demos/triggers): use hyphenated entity IDs to match gateway normalisation The gateway normalises app IDs with hyphens - the entity is registered as e.g. diagnostic-bridge, manipulation-monitor, not diagnostic_bridge. The trigger wrappers were assuming the ROS-node-name spelling (underscore), which matches the reporting_sources path inside the FaultEvent payload but not the URL-addressable entity ID. setup-triggers returned HTTP 404 from /apps//triggers and watch-triggers found no active trigger to subscribe to. Switch every wrapper to the hyphenated form. For turtlebot3 also switch from anomaly-detector to diagnostic-bridge: faults on this stack arrive from /diagnostics through the bridge and bucket against the bridge's faults collection, so the anomaly-detector trigger would never see an event even with the correct ID. --- demos/moveit_pick_place/setup-triggers.sh | 4 ++-- demos/moveit_pick_place/watch-triggers.sh | 2 +- demos/sensor_diagnostics/setup-triggers.sh | 4 ++-- demos/sensor_diagnostics/watch-triggers.sh | 2 +- demos/turtlebot3_integration/setup-triggers.sh | 7 ++++--- demos/turtlebot3_integration/watch-triggers.sh | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh index 06ff53e..3f2ed7b 100755 --- a/demos/moveit_pick_place/setup-triggers.sh +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -2,8 +2,8 @@ # Create fault-monitoring trigger for moveit pick-and-place demo # Alerts on any fault change reported by the manipulation monitor export ENTITY_TYPE="apps" -# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -export ENTITY_ID="manipulation_monitor" +# Gateway normalises entity IDs with hyphens (matches the registered apps id). +export ENTITY_ID="manipulation-monitor" export INJECT_HINT="./inject-planning-failure.sh" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/setup-trigger.sh" diff --git a/demos/moveit_pick_place/watch-triggers.sh b/demos/moveit_pick_place/watch-triggers.sh index 4cae6e7..53651bf 100755 --- a/demos/moveit_pick_place/watch-triggers.sh +++ b/demos/moveit_pick_place/watch-triggers.sh @@ -2,6 +2,6 @@ # Watch trigger events for moveit pick-and-place demo # Connects to SSE stream and prints fault events in real time export ENTITY_TYPE="apps" -export ENTITY_ID="manipulation_monitor" +export ENTITY_ID="manipulation-monitor" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/watch-trigger.sh" "$@" diff --git a/demos/sensor_diagnostics/setup-triggers.sh b/demos/sensor_diagnostics/setup-triggers.sh index f7dcbaf..7d731a0 100755 --- a/demos/sensor_diagnostics/setup-triggers.sh +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -2,8 +2,8 @@ # Create fault-monitoring trigger for sensor diagnostics demo # Alerts on any new fault reported via the diagnostic bridge export ENTITY_TYPE="apps" -# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -export ENTITY_ID="diagnostic_bridge" +# Gateway normalises entity IDs with hyphens (matches the registered apps id). +export ENTITY_ID="diagnostic-bridge" export INJECT_HINT="./inject-nan.sh" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/setup-trigger.sh" diff --git a/demos/sensor_diagnostics/watch-triggers.sh b/demos/sensor_diagnostics/watch-triggers.sh index 298f3b4..3fbd928 100755 --- a/demos/sensor_diagnostics/watch-triggers.sh +++ b/demos/sensor_diagnostics/watch-triggers.sh @@ -2,6 +2,6 @@ # Watch trigger events for sensor diagnostics demo # Connects to SSE stream and prints fault events in real time export ENTITY_TYPE="apps" -export ENTITY_ID="diagnostic_bridge" +export ENTITY_ID="diagnostic-bridge" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/watch-trigger.sh" "$@" diff --git a/demos/turtlebot3_integration/setup-triggers.sh b/demos/turtlebot3_integration/setup-triggers.sh index 8092c13..b9390f6 100755 --- a/demos/turtlebot3_integration/setup-triggers.sh +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -1,9 +1,10 @@ #!/bin/bash # Create fault-monitoring trigger for turtlebot3 integration demo -# Alerts on any fault change reported by the anomaly detector +# Alerts on any fault change reported via the diagnostic bridge - the +# anomaly-detector app has no faults of its own, faults arrive from +# /diagnostics through the bridge. export ENTITY_TYPE="apps" -# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -export ENTITY_ID="anomaly_detector" +export ENTITY_ID="diagnostic-bridge" export INJECT_HINT="./inject-nav-failure.sh" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/setup-trigger.sh" diff --git a/demos/turtlebot3_integration/watch-triggers.sh b/demos/turtlebot3_integration/watch-triggers.sh index 6f96330..d6d9b6c 100755 --- a/demos/turtlebot3_integration/watch-triggers.sh +++ b/demos/turtlebot3_integration/watch-triggers.sh @@ -2,6 +2,6 @@ # Watch trigger events for turtlebot3 integration demo # Connects to SSE stream and prints fault events in real time export ENTITY_TYPE="apps" -export ENTITY_ID="anomaly_detector" +export ENTITY_ID="diagnostic-bridge" # shellcheck disable=SC1091 source "$(cd "$(dirname "$0")" && pwd)/../../lib/watch-trigger.sh" "$@" From 9e5dc73fbf3d9e395860cc723137e5960e88ba5b Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 26 May 2026 16:54:52 +0200 Subject: [PATCH 3/6] fix(demos): relax nounset around ROS setup.bash sourcing Container scripts in moveit_pick_place and multi_ecu_aggregation start with "set -eu" and then source /opt/ros/jazzy/setup.bash. setup.bash dereferences AMENT_TRACE_SETUP_FILES without guarding for unset, so under nounset the source aborts before any payload runs: /opt/ros/jazzy/setup.bash: line 8: AMENT_TRACE_SETUP_FILES: unbound variable The Scripts API reports this as "Script exited with code 1" and the inject/restore never lands. Wrap the source pair with set +u / set -u so AMENT_* defaults can stay implicit while the rest of the script keeps strict variable checking. --- .../moveit-planning/inject-collision/script.bash | 3 +++ .../moveit-planning/inject-planning-failure/script.bash | 3 +++ .../moveit-planning/restore-normal/script.bash | 3 +++ .../actuation-ecu/inject-gripper-jam/script.bash | 3 +++ .../container_scripts/actuation-ecu/restore-normal/script.bash | 3 +++ .../perception-ecu/inject-sensor-failure/script.bash | 3 +++ .../perception-ecu/restore-normal/script.bash | 3 +++ .../planning-ecu/inject-planning-delay/script.bash | 3 +++ .../container_scripts/planning-ecu/restore-normal/script.bash | 3 +++ 9 files changed, 27 insertions(+) diff --git a/demos/moveit_pick_place/container_scripts/moveit-planning/inject-collision/script.bash b/demos/moveit_pick_place/container_scripts/moveit-planning/inject-collision/script.bash index 3985905..5e39fcc 100644 --- a/demos/moveit_pick_place/container_scripts/moveit-planning/inject-collision/script.bash +++ b/demos/moveit_pick_place/container_scripts/moveit-planning/inject-collision/script.bash @@ -5,10 +5,13 @@ set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u echo "Injecting COLLISION fault..." echo " Spawning surprise obstacle in robot workspace" diff --git a/demos/moveit_pick_place/container_scripts/moveit-planning/inject-planning-failure/script.bash b/demos/moveit_pick_place/container_scripts/moveit-planning/inject-planning-failure/script.bash index 18d8d8c..3e93a15 100644 --- a/demos/moveit_pick_place/container_scripts/moveit-planning/inject-planning-failure/script.bash +++ b/demos/moveit_pick_place/container_scripts/moveit-planning/inject-planning-failure/script.bash @@ -5,10 +5,13 @@ set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u echo "Injecting PLANNING FAILURE fault..." echo " Adding collision wall between pick and place positions" diff --git a/demos/moveit_pick_place/container_scripts/moveit-planning/restore-normal/script.bash b/demos/moveit_pick_place/container_scripts/moveit-planning/restore-normal/script.bash index a50d81a..e9e02d7 100644 --- a/demos/moveit_pick_place/container_scripts/moveit-planning/restore-normal/script.bash +++ b/demos/moveit_pick_place/container_scripts/moveit-planning/restore-normal/script.bash @@ -4,10 +4,13 @@ set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}" API_BASE="${GATEWAY_URL}/api/v1" diff --git a/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/inject-gripper-jam/script.bash b/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/inject-gripper-jam/script.bash index e0a2f64..6913093 100644 --- a/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/inject-gripper-jam/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/inject-gripper-jam/script.bash @@ -2,10 +2,13 @@ # Inject gripper jam - gripper controller stuck set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ros2 param set /actuation/gripper_controller inject_jam true diff --git a/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/restore-normal/script.bash b/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/restore-normal/script.bash index bb583e7..a7d6e72 100644 --- a/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/restore-normal/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/actuation-ecu/restore-normal/script.bash @@ -2,10 +2,13 @@ # Reset all actuation node parameters to defaults set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ERRORS=0 diff --git a/demos/multi_ecu_aggregation/container_scripts/perception-ecu/inject-sensor-failure/script.bash b/demos/multi_ecu_aggregation/container_scripts/perception-ecu/inject-sensor-failure/script.bash index 321148f..4d784ce 100644 --- a/demos/multi_ecu_aggregation/container_scripts/perception-ecu/inject-sensor-failure/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/perception-ecu/inject-sensor-failure/script.bash @@ -2,10 +2,13 @@ # Inject LiDAR sensor failure - high failure probability set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ros2 param set /perception/lidar_driver failure_probability 0.8 diff --git a/demos/multi_ecu_aggregation/container_scripts/perception-ecu/restore-normal/script.bash b/demos/multi_ecu_aggregation/container_scripts/perception-ecu/restore-normal/script.bash index 9f22b5b..23d7e73 100644 --- a/demos/multi_ecu_aggregation/container_scripts/perception-ecu/restore-normal/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/perception-ecu/restore-normal/script.bash @@ -2,10 +2,13 @@ # Reset all perception node parameters to defaults set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ERRORS=0 diff --git a/demos/multi_ecu_aggregation/container_scripts/planning-ecu/inject-planning-delay/script.bash b/demos/multi_ecu_aggregation/container_scripts/planning-ecu/inject-planning-delay/script.bash index d48225b..d186826 100644 --- a/demos/multi_ecu_aggregation/container_scripts/planning-ecu/inject-planning-delay/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/planning-ecu/inject-planning-delay/script.bash @@ -2,10 +2,13 @@ # Inject path planning delay - 5000ms processing time set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ros2 param set /planning/path_planner planning_delay_ms 5000 diff --git a/demos/multi_ecu_aggregation/container_scripts/planning-ecu/restore-normal/script.bash b/demos/multi_ecu_aggregation/container_scripts/planning-ecu/restore-normal/script.bash index 68e4933..4ff9ced 100644 --- a/demos/multi_ecu_aggregation/container_scripts/planning-ecu/restore-normal/script.bash +++ b/demos/multi_ecu_aggregation/container_scripts/planning-ecu/restore-normal/script.bash @@ -2,10 +2,13 @@ # Reset all planning node parameters to defaults set -eu +# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it. +set +u # shellcheck source=/dev/null source /opt/ros/jazzy/setup.bash # shellcheck source=/dev/null source /root/demo_ws/install/setup.bash +set -u ERRORS=0 From 24e0495d58c35e583a61401db6c09ece5304bd1b Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 26 May 2026 16:55:04 +0200 Subject: [PATCH 4/6] fix(demos/tb3): treat nav2 action rejection as expected in inject-localization-failure The script reinitialises AMCL (scatters particles) and then asks bt-navigator to navigate. Under scattered particles the action server returns HTTP 400 with x-medkit-ros2-action-rejected - which is the demo's intended failure mode, the whole point of the injection. curl -sf turns that 400 into exit 22 and aborts the script before the final "Localization failure injected." print, and the Scripts API reports the script as failed even though the fault path it was supposed to trigger fired correctly. Drop -f on the navigate_to_pose call and print the response body (matching the pattern used in inject-nav-failure on the same demo). --- .../nav2-stack/inject-localization-failure/script.bash | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/demos/turtlebot3_integration/container_scripts/nav2-stack/inject-localization-failure/script.bash b/demos/turtlebot3_integration/container_scripts/nav2-stack/inject-localization-failure/script.bash index c34b396..64a3f57 100755 --- a/demos/turtlebot3_integration/container_scripts/nav2-stack/inject-localization-failure/script.bash +++ b/demos/turtlebot3_integration/container_scripts/nav2-stack/inject-localization-failure/script.bash @@ -15,7 +15,9 @@ echo "Waiting for particles to scatter..." sleep 2 echo "Sending navigation goal with high localization uncertainty..." -curl -sf -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" \ +# Drop -f: action server typically rejects the goal under scattered particles, +# which is the demo's intended failure mode - treat HTTP 400 as expected. +RESPONSE=$(curl -s -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" \ -H "Content-Type: application/json" \ -d '{ "goal": { @@ -27,7 +29,8 @@ curl -sf -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/exec } } } - }' + }') +echo "${RESPONSE}" | jq '.' 2>/dev/null || echo "${RESPONSE}" echo "" echo "Localization failure injected." From 29e786dac8be79034af0ff4f7c054dbaca267a9a Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 26 May 2026 16:55:16 +0200 Subject: [PATCH 5/6] chore(demos): restore exec bit on injection/diagnostic scripts Four host-side helper scripts lost their executable bit somewhere along the way: demos/sensor_diagnostics/inject-fault-scenario.sh demos/sensor_diagnostics/run-diagnostics.sh demos/moveit_pick_place/arm-self-test.sh demos/moveit_pick_place/planning-benchmark.sh README and run-demo.sh both reference them as ./script.sh, so running the demos as documented hits "Permission denied". chmod +x to restore the bit; no content change. --- demos/moveit_pick_place/arm-self-test.sh | 0 demos/moveit_pick_place/planning-benchmark.sh | 0 demos/sensor_diagnostics/inject-fault-scenario.sh | 0 demos/sensor_diagnostics/run-diagnostics.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 demos/moveit_pick_place/arm-self-test.sh mode change 100644 => 100755 demos/moveit_pick_place/planning-benchmark.sh mode change 100644 => 100755 demos/sensor_diagnostics/inject-fault-scenario.sh mode change 100644 => 100755 demos/sensor_diagnostics/run-diagnostics.sh diff --git a/demos/moveit_pick_place/arm-self-test.sh b/demos/moveit_pick_place/arm-self-test.sh old mode 100644 new mode 100755 diff --git a/demos/moveit_pick_place/planning-benchmark.sh b/demos/moveit_pick_place/planning-benchmark.sh old mode 100644 new mode 100755 diff --git a/demos/sensor_diagnostics/inject-fault-scenario.sh b/demos/sensor_diagnostics/inject-fault-scenario.sh old mode 100644 new mode 100755 diff --git a/demos/sensor_diagnostics/run-diagnostics.sh b/demos/sensor_diagnostics/run-diagnostics.sh old mode 100644 new mode 100755 From c33845ebde684bb141c72e9694363577125e7402 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Fri, 29 May 2026 21:49:14 +0200 Subject: [PATCH 6/6] fix(tests/multi-ecu): wait for actuation ECU aggregation before asserting The readiness gate polled only for the planning ECU's path-planner app, then asserted on all three peer ECUs. When the actuation ECU was slower to aggregate into robot-alpha, the apps assertion ran before its nodes were linked and reported "found 7" (perception + planning only), failing build-and-test-multi-ecu intermittently. The gate now waits for a representative app from each peer ECU (path-planner for planning, motor-controller for actuation) before the discovery assertions run. --- tests/smoke_test_multi_ecu.sh | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/smoke_test_multi_ecu.sh b/tests/smoke_test_multi_ecu.sh index 3a5e2a2..8fec105 100755 --- a/tests/smoke_test_multi_ecu.sh +++ b/tests/smoke_test_multi_ecu.sh @@ -26,14 +26,25 @@ wait_for_gateway 120 # Wait for runtime node linking (perception ECU local nodes) wait_for_runtime_linking "/apps/lidar-driver/data" 90 -# Wait for aggregation to discover peer ECUs -echo " Waiting for aggregated entities from planning ECU (max 60s)..." -if poll_until "/apps" '.items[] | select(.id == "path-planner")' 60; then - echo -e " ${GREEN}Aggregation discovery complete${NC}" -else - echo -e " ${RED}Aggregation discovery did not complete within 60s${NC}" - exit 1 -fi +# Wait for aggregation to discover peer ECUs. +# The aggregator pulls each peer independently, so a single peer's marker app is +# not a sufficient readiness gate: we must see a representative app from EACH +# peer ECU before the discovery assertions run. Otherwise a slower peer +# (typically the actuation ECU) races the checks and surfaces as missing apps +# ("found 7" instead of >=10). +# planning ECU -> path-planner +# actuation ECU -> motor-controller +echo " Waiting for aggregated entities from planning + actuation ECUs (max 60s each)..." +for peer in "planning ECU:path-planner" "actuation ECU:motor-controller"; do + peer_name="${peer%%:*}" + peer_app="${peer##*:}" + if poll_until "/apps" ".items[] | select(.id == \"${peer_app}\")" 60; then + echo -e " ${GREEN}${peer_name} aggregated (${peer_app})${NC}" + else + echo -e " ${RED}${peer_name} not aggregated within 60s (${peer_app} missing)${NC}" + exit 1 + fi +done # --- Tests ---