From 47120facadcd1c9777a934551180ac874796c522 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 20:44:15 +0100 Subject: [PATCH 01/16] feat: add shared triggers API library for demos --- lib/triggers-api.sh | 166 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 lib/triggers-api.sh diff --git a/lib/triggers-api.sh b/lib/triggers-api.sh new file mode 100644 index 0000000..6f11d9e --- /dev/null +++ b/lib/triggers-api.sh @@ -0,0 +1,166 @@ +#!/bin/bash +# Shared helper for gateway Triggers API +# Source this from host-side trigger scripts: +# SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# source "${SCRIPT_DIR}/../../lib/triggers-api.sh" +# +# Environment variables: +# GATEWAY_URL - Gateway base URL (default: http://localhost:8080) + +GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}" +API_BASE="${GATEWAY_URL}/api/v1" + +# Check dependencies +for cmd in curl jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Required tool '$cmd' is not installed." + exit 1 + fi +done + +# Check gateway is reachable +check_gateway() { + if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then + echo "Gateway not available at ${GATEWAY_URL}. Is the demo running? Try: ./run-demo.sh" + exit 1 + fi +} + +# Create an OnChange trigger on an entity's faults collection +# Usage: create_fault_trigger [lifetime] +# Returns: trigger JSON on stdout +create_fault_trigger() { + local entity_type="$1" + local entity_id="$2" + local lifetime="${3:-3600}" + + check_gateway + + local resource="${API_BASE}/${entity_type}/${entity_id}/faults" + local body + body=$(jq -n \ + --arg resource "$resource" \ + --argjson lifetime "$lifetime" \ + '{ + resource: $resource, + trigger_condition: {condition_type: "OnChange"}, + multishot: true, + lifetime: $lifetime + }') + + local result + local http_code + result=$(curl -s -w "\n%{http_code}" -X POST \ + "${API_BASE}/${entity_type}/${entity_id}/triggers" \ + -H "Content-Type: application/json" \ + -d "$body" 2>/dev/null) || true + + http_code=$(echo "$result" | tail -1) + result=$(echo "$result" | sed '$d') + + if [ "$http_code" != "201" ]; then + echo "Failed to create trigger (HTTP $http_code):" >&2 + echo "$result" | jq '.' 2>/dev/null >&2 || echo "$result" >&2 + return 1 + fi + + echo "$result" +} + +# List triggers for an entity +# Usage: list_triggers +list_triggers() { + local entity_type="$1" + local entity_id="$2" + + check_gateway + + curl -sf "${API_BASE}/${entity_type}/${entity_id}/triggers" 2>/dev/null | jq '.' +} + +# Delete a trigger +# Usage: delete_trigger +delete_trigger() { + local entity_type="$1" + local entity_id="$2" + local trigger_id="$3" + + check_gateway + + local http_code + http_code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + "${API_BASE}/${entity_type}/${entity_id}/triggers/${trigger_id}" 2>/dev/null) || true + + if [ "$http_code" = "204" ]; then + echo "Trigger ${trigger_id} deleted." + else + echo "Failed to delete trigger ${trigger_id} (HTTP $http_code)." >&2 + return 1 + fi +} + +# Watch SSE events for a trigger (blocking - Ctrl+C to stop) +# Usage: watch_trigger_events +watch_trigger_events() { + local entity_type="$1" + local entity_id="$2" + local trigger_id="$3" + + check_gateway + + local url="${API_BASE}/${entity_type}/${entity_id}/triggers/${trigger_id}/events" + + echo "Listening for trigger events..." + echo " Entity: ${entity_type}/${entity_id}" + echo " Trigger: ${trigger_id}" + echo " SSE URL: ${url}" + echo "" + echo "Waiting for events (Ctrl+C to stop)..." + echo "---" + + # Stream SSE events, filter out keepalives, pretty-print JSON data lines + curl -sf -N "${url}" 2>/dev/null | while IFS= read -r line; do + # Skip empty lines and keepalive comments + if [ -z "$line" ] || [[ "$line" == ":"* ]]; then + continue + fi + # Parse SSE data lines + if [[ "$line" == data:* ]]; then + local data="${line#data:}" + # Trim leading space if present + data="${data# }" + local timestamp + timestamp=$(echo "$data" | jq -r '.timestamp // empty' 2>/dev/null) + if [ -n "$timestamp" ]; then + echo "[${timestamp}] Event received:" + echo "$data" | jq '.' 2>/dev/null || echo "$data" + echo "---" + else + echo "$data" | jq '.' 2>/dev/null || echo "$data" + echo "---" + fi + fi + done +} + +# Find the first active trigger for an entity +# Usage: find_active_trigger +# Returns: trigger_id on stdout, or empty string if none found +find_active_trigger() { + local entity_type="$1" + local entity_id="$2" + + check_gateway + + local result + result=$(curl -sf "${API_BASE}/${entity_type}/${entity_id}/triggers" 2>/dev/null) || true + + if [ -z "$result" ]; then + echo "" + return 0 + fi + + local tid + tid=$(echo "$result" | jq -r '.items[] | select(.status == "active") | .id' 2>/dev/null | head -1) || true + echo "${tid:-}" +} From 3ff1ccd745ecd63ed514ad08f72853ef81f72b72 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 20:49:40 +0100 Subject: [PATCH 02/16] feat(sensor_diagnostics): add trigger showcase scripts --- demos/sensor_diagnostics/setup-triggers.sh | 29 ++++++++++++++++++++++ demos/sensor_diagnostics/watch-triggers.sh | 26 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100755 demos/sensor_diagnostics/setup-triggers.sh create mode 100755 demos/sensor_diagnostics/watch-triggers.sh diff --git a/demos/sensor_diagnostics/setup-triggers.sh b/demos/sensor_diagnostics/setup-triggers.sh new file mode 100755 index 0000000..3398384 --- /dev/null +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Create fault-monitoring trigger for sensor diagnostics demo +# Alerts on any new fault reported on the compute-unit component +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="compute-unit" + +echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." +echo "" + +result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") +trigger_id=$(echo "$result" | jq -r '.id') +status=$(echo "$result" | jq -r '.status') +event_source=$(echo "$result" | jq -r '.event_source') + +echo "Trigger created successfully!" +echo " ID: ${trigger_id}" +echo " Status: ${status}" +echo " Events: ${GATEWAY_URL}${event_source}" +echo "" +echo "To watch for events:" +echo " ./watch-triggers.sh" +echo "" +echo "Then inject a fault in another terminal:" +echo " ./inject-nan.sh" diff --git a/demos/sensor_diagnostics/watch-triggers.sh b/demos/sensor_diagnostics/watch-triggers.sh new file mode 100755 index 0000000..01cd26a --- /dev/null +++ b/demos/sensor_diagnostics/watch-triggers.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Watch trigger events for sensor diagnostics demo +# Connects to SSE stream and prints fault events in real time +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="compute-unit" + +trigger_id="${1:-}" + +if [ -z "$trigger_id" ]; then + # Auto-detect: find first active trigger + trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") + if [ -z "$trigger_id" ]; then + echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." + echo "Create one first: ./setup-triggers.sh" + exit 1 + fi + echo "Found active trigger: ${trigger_id}" + echo "" +fi + +watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" From 1dec87bd395546d3fb9e4a064efe3652692ecaf5 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 20:51:02 +0100 Subject: [PATCH 03/16] feat(turtlebot3): add trigger showcase scripts --- .../turtlebot3_integration/setup-triggers.sh | 29 +++++++++++++++++++ .../turtlebot3_integration/watch-triggers.sh | 26 +++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100755 demos/turtlebot3_integration/setup-triggers.sh create mode 100755 demos/turtlebot3_integration/watch-triggers.sh diff --git a/demos/turtlebot3_integration/setup-triggers.sh b/demos/turtlebot3_integration/setup-triggers.sh new file mode 100755 index 0000000..6d41a83 --- /dev/null +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Create fault-monitoring trigger for turtlebot3 integration demo +# Alerts on navigation failure faults on the nav2-stack component +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="nav2-stack" + +echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." +echo "" + +result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") +trigger_id=$(echo "$result" | jq -r '.id') +status=$(echo "$result" | jq -r '.status') +event_source=$(echo "$result" | jq -r '.event_source') + +echo "Trigger created successfully!" +echo " ID: ${trigger_id}" +echo " Status: ${status}" +echo " Events: ${GATEWAY_URL}${event_source}" +echo "" +echo "To watch for events:" +echo " ./watch-triggers.sh" +echo "" +echo "Then inject a fault in another terminal:" +echo " ./inject-nav-failure.sh" diff --git a/demos/turtlebot3_integration/watch-triggers.sh b/demos/turtlebot3_integration/watch-triggers.sh new file mode 100755 index 0000000..2ac4254 --- /dev/null +++ b/demos/turtlebot3_integration/watch-triggers.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Watch trigger events for turtlebot3 integration demo +# Connects to SSE stream and prints fault events in real time +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="nav2-stack" + +trigger_id="${1:-}" + +if [ -z "$trigger_id" ]; then + # Auto-detect: find first active trigger + trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") + if [ -z "$trigger_id" ]; then + echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." + echo "Create one first: ./setup-triggers.sh" + exit 1 + fi + echo "Found active trigger: ${trigger_id}" + echo "" +fi + +watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" From 9908af85b9b0acb616502148fe43ee6dec51220d Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:04:34 +0100 Subject: [PATCH 04/16] feat(moveit): add trigger showcase scripts --- demos/moveit_pick_place/setup-triggers.sh | 29 +++++++++++++++++++++++ demos/moveit_pick_place/watch-triggers.sh | 26 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100755 demos/moveit_pick_place/setup-triggers.sh create mode 100755 demos/moveit_pick_place/watch-triggers.sh diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh new file mode 100755 index 0000000..df24605 --- /dev/null +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Create fault-monitoring trigger for moveit pick-and-place demo +# Alerts on planning failure faults on the moveit-planning component +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="moveit-planning" + +echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." +echo "" + +result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") +trigger_id=$(echo "$result" | jq -r '.id') +status=$(echo "$result" | jq -r '.status') +event_source=$(echo "$result" | jq -r '.event_source') + +echo "Trigger created successfully!" +echo " ID: ${trigger_id}" +echo " Status: ${status}" +echo " Events: ${GATEWAY_URL}${event_source}" +echo "" +echo "To watch for events:" +echo " ./watch-triggers.sh" +echo "" +echo "Then inject a fault in another terminal:" +echo " ./inject-planning-failure.sh" diff --git a/demos/moveit_pick_place/watch-triggers.sh b/demos/moveit_pick_place/watch-triggers.sh new file mode 100755 index 0000000..7045028 --- /dev/null +++ b/demos/moveit_pick_place/watch-triggers.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Watch trigger events for moveit pick-and-place demo +# Connects to SSE stream and prints fault events in real time +set -eu +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/../../lib/triggers-api.sh" + +ENTITY_TYPE="components" +ENTITY_ID="moveit-planning" + +trigger_id="${1:-}" + +if [ -z "$trigger_id" ]; then + # Auto-detect: find first active trigger + trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") + if [ -z "$trigger_id" ]; then + echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." + echo "Create one first: ./setup-triggers.sh" + exit 1 + fi + echo "Found active trigger: ${trigger_id}" + echo "" +fi + +watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" From 8469c72b05b0ca31fe0030069c8dd30567a63618 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:05:56 +0100 Subject: [PATCH 05/16] feat: add trigger commands to run-demo.sh daemon output --- demos/moveit_pick_place/run-demo.sh | 4 ++++ demos/sensor_diagnostics/run-demo.sh | 4 ++++ demos/turtlebot3_integration/run-demo.sh | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/demos/moveit_pick_place/run-demo.sh b/demos/moveit_pick_place/run-demo.sh index ad7baca..fd2e2fb 100755 --- a/demos/moveit_pick_place/run-demo.sh +++ b/demos/moveit_pick_place/run-demo.sh @@ -166,5 +166,9 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " docker exec -it moveit_medkit_demo bash # CPU" echo " docker exec -it moveit_medkit_demo_nvidia bash # NVIDIA" echo "" + echo "📡 Triggers (condition-based alerts):" + echo " ./setup-triggers.sh # Create fault alert trigger" + echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo "" echo "🛑 To stop: ./stop-demo.sh" fi diff --git a/demos/sensor_diagnostics/run-demo.sh b/demos/sensor_diagnostics/run-demo.sh index c6e9a30..6b03f9c 100755 --- a/demos/sensor_diagnostics/run-demo.sh +++ b/demos/sensor_diagnostics/run-demo.sh @@ -126,6 +126,10 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " ./inject-drift.sh # Inject sensor drift" echo " ./restore-normal.sh # Restore normal operation" echo "" + echo "📡 Triggers (condition-based alerts):" + echo " ./setup-triggers.sh # Create fault alert trigger" + echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo "" echo "🌐 Web UI: http://localhost:3000" echo "🌐 REST API: http://localhost:8080/api/v1/" echo "" diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh index 3af1a39..a11113b 100755 --- a/demos/turtlebot3_integration/run-demo.sh +++ b/demos/turtlebot3_integration/run-demo.sh @@ -173,5 +173,9 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " docker exec -it turtlebot3_medkit_demo bash # CPU" echo " docker exec -it turtlebot3_medkit_demo_nvidia bash # NVIDIA" echo "" + echo "📡 Triggers (condition-based alerts):" + echo " ./setup-triggers.sh # Create fault alert trigger" + echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo "" echo "🛑 To stop: ./stop-demo.sh" fi From 747fc6158075e93b9646e3ea43ead0132f11cc05 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:07:12 +0100 Subject: [PATCH 06/16] docs: add triggers showcase documentation to all demos --- demos/moveit_pick_place/README.md | 51 ++++++++++++++++++++++++++ demos/sensor_diagnostics/README.md | 51 ++++++++++++++++++++++++++ demos/turtlebot3_integration/README.md | 51 ++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/demos/moveit_pick_place/README.md b/demos/moveit_pick_place/README.md index 6e79903..9374e10 100644 --- a/demos/moveit_pick_place/README.md +++ b/demos/moveit_pick_place/README.md @@ -284,6 +284,57 @@ GATEWAY_URL=http://192.168.1.10:8080 ./inject-collision.sh The host-side wrapper scripts (`./inject-collision.sh`, etc.) call the Scripts API automatically - no `docker exec` needed. Prerequisites: `curl` and `jq` must be installed on the host. +## Triggers (Condition-Based Alerts) + +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on planning failure faults on the moveit-planning component. + +### Setup + +```bash +# Terminal 1: Start the demo +./run-demo.sh + +# Terminal 2: Create fault trigger and watch for events +./setup-triggers.sh # Creates OnChange trigger on moveit-planning faults +./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) + +# Terminal 3: Inject a fault - trigger fires! +./inject-planning-failure.sh +``` + +### How It Works + +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/moveit-planning/triggers`: + - **Resource:** `/api/v1/components/moveit-planning/faults` (watches fault collection) + - **Condition:** `OnChange` (fires on any new or updated fault) + - **Multishot:** `true` (fires repeatedly, not just once) + - **Lifetime:** 3600 seconds (auto-expires after 1 hour) +2. `watch-triggers.sh` connects to the SSE event stream at the trigger's `event_source` URL +3. When a fault is injected and detected by the gateway, the trigger fires and an SSE event is delivered + +### Manual API Usage + +```bash +# Create a trigger +curl -X POST http://localhost:8080/api/v1/components/moveit-planning/triggers \ + -H "Content-Type: application/json" \ + -d '{ + "resource": "/api/v1/components/moveit-planning/faults", + "trigger_condition": {"condition_type": "OnChange"}, + "multishot": true, + "lifetime": 3600 + }' | jq + +# List triggers +curl http://localhost:8080/api/v1/components/moveit-planning/triggers | jq + +# Watch events (replace TRIGGER_ID) +curl -N http://localhost:8080/api/v1/components/moveit-planning/triggers/TRIGGER_ID/events + +# Delete a trigger +curl -X DELETE http://localhost:8080/api/v1/components/moveit-planning/triggers/TRIGGER_ID +``` + ## Fault Injection Scenarios The fault injection scripts run inside the container via the Scripts API. The host-side `./inject-*.sh` and `./restore-normal.sh` wrappers call the gateway REST endpoint - no `docker exec` required. diff --git a/demos/sensor_diagnostics/README.md b/demos/sensor_diagnostics/README.md index c6db136..2aea857 100644 --- a/demos/sensor_diagnostics/README.md +++ b/demos/sensor_diagnostics/README.md @@ -184,6 +184,57 @@ GATEWAY_URL=http://192.168.1.10:8080 ./inject-nan.sh | `inject-noise` | Inject high noise on LiDAR and Camera | | `restore-normal` | Reset all sensors and clear faults | +## Triggers (Condition-Based Alerts) + +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts whenever a new fault is reported. + +### Setup + +```bash +# Terminal 1: Start the demo +./run-demo.sh + +# Terminal 2: Create fault trigger and watch for events +./setup-triggers.sh # Creates OnChange trigger on compute-unit faults +./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) + +# Terminal 3: Inject a fault - trigger fires! +./inject-nan.sh +``` + +### How It Works + +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/compute-unit/triggers`: + - **Resource:** `/api/v1/components/compute-unit/faults` (watches fault collection) + - **Condition:** `OnChange` (fires on any new or updated fault) + - **Multishot:** `true` (fires repeatedly, not just once) + - **Lifetime:** 3600 seconds (auto-expires after 1 hour) +2. `watch-triggers.sh` connects to the SSE event stream at the trigger's `event_source` URL +3. When a fault is injected and detected by the gateway, the trigger fires and an SSE event is delivered + +### Manual API Usage + +```bash +# Create a trigger +curl -X POST http://localhost:8080/api/v1/components/compute-unit/triggers \ + -H "Content-Type: application/json" \ + -d '{ + "resource": "/api/v1/components/compute-unit/faults", + "trigger_condition": {"condition_type": "OnChange"}, + "multishot": true, + "lifetime": 3600 + }' | jq + +# List triggers +curl http://localhost:8080/api/v1/components/compute-unit/triggers | jq + +# Watch events (replace TRIGGER_ID) +curl -N http://localhost:8080/api/v1/components/compute-unit/triggers/TRIGGER_ID/events + +# Delete a trigger +curl -X DELETE http://localhost:8080/api/v1/components/compute-unit/triggers/TRIGGER_ID +``` + ## API Examples ### Read Sensor Data diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 079e156..b4e9ca3 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -400,6 +400,57 @@ GATEWAY_URL=http://192.168.1.10:8080 ./inject-nav-failure.sh | `inject-nav-failure` | Inject navigation failure (unreachable goal) | | `restore-normal` | Reset parameters and clear faults | +## Triggers (Condition-Based Alerts) + +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on navigation failure faults on the nav2-stack component. + +### Setup + +```bash +# Terminal 1: Start the demo +./run-demo.sh + +# Terminal 2: Create fault trigger and watch for events +./setup-triggers.sh # Creates OnChange trigger on nav2-stack faults +./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) + +# Terminal 3: Inject a fault - trigger fires! +./inject-nav-failure.sh +``` + +### How It Works + +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/nav2-stack/triggers`: + - **Resource:** `/api/v1/components/nav2-stack/faults` (watches fault collection) + - **Condition:** `OnChange` (fires on any new or updated fault) + - **Multishot:** `true` (fires repeatedly, not just once) + - **Lifetime:** 3600 seconds (auto-expires after 1 hour) +2. `watch-triggers.sh` connects to the SSE event stream at the trigger's `event_source` URL +3. When a fault is injected and detected by the gateway, the trigger fires and an SSE event is delivered + +### Manual API Usage + +```bash +# Create a trigger +curl -X POST http://localhost:8080/api/v1/components/nav2-stack/triggers \ + -H "Content-Type: application/json" \ + -d '{ + "resource": "/api/v1/components/nav2-stack/faults", + "trigger_condition": {"condition_type": "OnChange"}, + "multishot": true, + "lifetime": 3600 + }' | jq + +# List triggers +curl http://localhost:8080/api/v1/components/nav2-stack/triggers | jq + +# Watch events (replace TRIGGER_ID) +curl -N http://localhost:8080/api/v1/components/nav2-stack/triggers/TRIGGER_ID/events + +# Delete a trigger +curl -X DELETE http://localhost:8080/api/v1/components/nav2-stack/triggers/TRIGGER_ID +``` + ## Fault Injection Scenarios This demo includes scripts to inject various fault conditions for testing fault management. From 0e505186a200756b519ab2a46c64248921842988 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:20:38 +0100 Subject: [PATCH 07/16] fix: use API path instead of full URL in trigger resource field --- lib/triggers-api.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/triggers-api.sh b/lib/triggers-api.sh index 6f11d9e..ec5d069 100644 --- a/lib/triggers-api.sh +++ b/lib/triggers-api.sh @@ -36,7 +36,7 @@ create_fault_trigger() { check_gateway - local resource="${API_BASE}/${entity_type}/${entity_id}/faults" + local resource="/api/v1/${entity_type}/${entity_id}/faults" local body body=$(jq -n \ --arg resource "$resource" \ From ebbc28e04eb4ba37df6e02e2af98262477fb0390 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:29:31 +0100 Subject: [PATCH 08/16] fix: add connect-timeout and close message to SSE watcher --- lib/triggers-api.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/triggers-api.sh b/lib/triggers-api.sh index ec5d069..3ec7334 100644 --- a/lib/triggers-api.sh +++ b/lib/triggers-api.sh @@ -119,7 +119,7 @@ watch_trigger_events() { echo "---" # Stream SSE events, filter out keepalives, pretty-print JSON data lines - curl -sf -N "${url}" 2>/dev/null | while IFS= read -r line; do + curl -sf -N --connect-timeout 10 "${url}" 2>/dev/null | while IFS= read -r line; do # Skip empty lines and keepalive comments if [ -z "$line" ] || [[ "$line" == ":"* ]]; then continue @@ -141,6 +141,10 @@ watch_trigger_events() { fi fi done + + echo "" + echo "SSE stream closed. The trigger may have expired or the gateway restarted." + echo "Re-run ./setup-triggers.sh and ./watch-triggers.sh to reconnect." } # Find the first active trigger for an entity From 0e4ce5a029feac77e94bb9bfddc888fdc5e475b8 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:31:44 +0100 Subject: [PATCH 09/16] fix: add idempotency guard and response validation to setup-triggers --- demos/moveit_pick_place/setup-triggers.sh | 18 +++++++++++++++++- demos/sensor_diagnostics/setup-triggers.sh | 18 +++++++++++++++++- demos/turtlebot3_integration/setup-triggers.sh | 18 +++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh index df24605..73fe819 100755 --- a/demos/moveit_pick_place/setup-triggers.sh +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -9,11 +9,27 @@ source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="components" ENTITY_ID="moveit-planning" +# Check for existing active trigger +existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") +if [ -n "$existing" ]; then + echo "Active trigger already exists: ${existing}" + echo "Run ./watch-triggers.sh to connect, or delete it first:" + echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" + exit 0 +fi + echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." echo "" result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") trigger_id=$(echo "$result" | jq -r '.id') + +if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then + echo "Failed to parse trigger response." >&2 + echo "$result" >&2 + exit 1 +fi + status=$(echo "$result" | jq -r '.status') event_source=$(echo "$result" | jq -r '.event_source') @@ -23,7 +39,7 @@ echo " Status: ${status}" echo " Events: ${GATEWAY_URL}${event_source}" echo "" echo "To watch for events:" -echo " ./watch-triggers.sh" +echo " ./watch-triggers.sh ${trigger_id}" echo "" echo "Then inject a fault in another terminal:" echo " ./inject-planning-failure.sh" diff --git a/demos/sensor_diagnostics/setup-triggers.sh b/demos/sensor_diagnostics/setup-triggers.sh index 3398384..48765ed 100755 --- a/demos/sensor_diagnostics/setup-triggers.sh +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -9,11 +9,27 @@ source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="components" ENTITY_ID="compute-unit" +# Check for existing active trigger +existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") +if [ -n "$existing" ]; then + echo "Active trigger already exists: ${existing}" + echo "Run ./watch-triggers.sh to connect, or delete it first:" + echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" + exit 0 +fi + echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." echo "" result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") trigger_id=$(echo "$result" | jq -r '.id') + +if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then + echo "Failed to parse trigger response." >&2 + echo "$result" >&2 + exit 1 +fi + status=$(echo "$result" | jq -r '.status') event_source=$(echo "$result" | jq -r '.event_source') @@ -23,7 +39,7 @@ echo " Status: ${status}" echo " Events: ${GATEWAY_URL}${event_source}" echo "" echo "To watch for events:" -echo " ./watch-triggers.sh" +echo " ./watch-triggers.sh ${trigger_id}" echo "" echo "Then inject a fault in another terminal:" echo " ./inject-nan.sh" diff --git a/demos/turtlebot3_integration/setup-triggers.sh b/demos/turtlebot3_integration/setup-triggers.sh index 6d41a83..d076afc 100755 --- a/demos/turtlebot3_integration/setup-triggers.sh +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -9,11 +9,27 @@ source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="components" ENTITY_ID="nav2-stack" +# Check for existing active trigger +existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") +if [ -n "$existing" ]; then + echo "Active trigger already exists: ${existing}" + echo "Run ./watch-triggers.sh to connect, or delete it first:" + echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" + exit 0 +fi + echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." echo "" result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") trigger_id=$(echo "$result" | jq -r '.id') + +if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then + echo "Failed to parse trigger response." >&2 + echo "$result" >&2 + exit 1 +fi + status=$(echo "$result" | jq -r '.status') event_source=$(echo "$result" | jq -r '.event_source') @@ -23,7 +39,7 @@ echo " Status: ${status}" echo " Events: ${GATEWAY_URL}${event_source}" echo "" echo "To watch for events:" -echo " ./watch-triggers.sh" +echo " ./watch-triggers.sh ${trigger_id}" echo "" echo "Then inject a fault in another terminal:" echo " ./inject-nav-failure.sh" From 2d2a158d2a49c3c5a94d567b8a4985b818a10758 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:33:01 +0100 Subject: [PATCH 10/16] fix: add ordering guidance to trigger hints in run-demo.sh --- demos/moveit_pick_place/run-demo.sh | 5 +++-- demos/sensor_diagnostics/run-demo.sh | 5 +++-- demos/turtlebot3_integration/run-demo.sh | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/demos/moveit_pick_place/run-demo.sh b/demos/moveit_pick_place/run-demo.sh index fd2e2fb..6b7a0a5 100755 --- a/demos/moveit_pick_place/run-demo.sh +++ b/demos/moveit_pick_place/run-demo.sh @@ -167,8 +167,9 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " docker exec -it moveit_medkit_demo_nvidia bash # NVIDIA" echo "" echo "📡 Triggers (condition-based alerts):" - echo " ./setup-triggers.sh # Create fault alert trigger" - echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo " 1. ./setup-triggers.sh # Create fault alert trigger" + echo " 2. ./watch-triggers.sh # Watch events in a new terminal (blocking)" + echo " 3. ./inject-planning-failure.sh # Inject a fault to fire the trigger" echo "" echo "🛑 To stop: ./stop-demo.sh" fi diff --git a/demos/sensor_diagnostics/run-demo.sh b/demos/sensor_diagnostics/run-demo.sh index 6b03f9c..1559a6a 100755 --- a/demos/sensor_diagnostics/run-demo.sh +++ b/demos/sensor_diagnostics/run-demo.sh @@ -127,8 +127,9 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " ./restore-normal.sh # Restore normal operation" echo "" echo "📡 Triggers (condition-based alerts):" - echo " ./setup-triggers.sh # Create fault alert trigger" - echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo " 1. ./setup-triggers.sh # Create fault alert trigger" + echo " 2. ./watch-triggers.sh # Watch events in a new terminal (blocking)" + echo " 3. ./inject-nan.sh # Inject a fault to fire the trigger" echo "" echo "🌐 Web UI: http://localhost:3000" echo "🌐 REST API: http://localhost:8080/api/v1/" diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh index a11113b..0b0dbf2 100755 --- a/demos/turtlebot3_integration/run-demo.sh +++ b/demos/turtlebot3_integration/run-demo.sh @@ -174,8 +174,9 @@ if [[ "$DETACH_MODE" == "true" ]]; then echo " docker exec -it turtlebot3_medkit_demo_nvidia bash # NVIDIA" echo "" echo "📡 Triggers (condition-based alerts):" - echo " ./setup-triggers.sh # Create fault alert trigger" - echo " ./watch-triggers.sh # Watch trigger events (SSE stream)" + echo " 1. ./setup-triggers.sh # Create fault alert trigger" + echo " 2. ./watch-triggers.sh # Watch events in a new terminal (blocking)" + echo " 3. ./inject-nav-failure.sh # Inject a fault to fire the trigger" echo "" echo "🛑 To stop: ./stop-demo.sh" fi From b2667a580e4a084829657bb02df935d5337a161b Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 21:44:50 +0100 Subject: [PATCH 11/16] fix: improve README terminal workflow, add scripts to tables, clean em dashes --- demos/moveit_pick_place/README.md | 68 ++++++++++++++------------ demos/sensor_diagnostics/README.md | 12 +++-- demos/turtlebot3_integration/README.md | 12 +++-- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/demos/moveit_pick_place/README.md b/demos/moveit_pick_place/README.md index 9374e10..46343d2 100644 --- a/demos/moveit_pick_place/README.md +++ b/demos/moveit_pick_place/README.md @@ -1,10 +1,10 @@ # MoveIt 2 Pick-and-Place Integration Demo -A comprehensive integration demo combining a **Panda 7-DOF robot arm** with **MoveIt 2** motion planning and **ros2_medkit** SOVD-compliant diagnostics. The robot performs continuous pick-and-place cycles in a **Gazebo Harmonic factory scene** while a manipulation monitor detects faults — planning failures, collisions — and reports them through the SOVD REST API with environment snapshots. +A comprehensive integration demo combining a **Panda 7-DOF robot arm** with **MoveIt 2** motion planning and **ros2_medkit** SOVD-compliant diagnostics. The robot performs continuous pick-and-place cycles in a **Gazebo Harmonic factory scene** while a manipulation monitor detects faults - planning failures, collisions - and reports them through the SOVD REST API with environment snapshots. ## Status -✅ **Demo Ready** — Docker-based deployment with MoveIt 2, Gazebo Harmonic physics simulation, factory environment, and full ros2_medkit stack. +✅ **Demo Ready** - Docker-based deployment with MoveIt 2, Gazebo Harmonic physics simulation, factory environment, and full ros2_medkit stack. ## Overview @@ -14,7 +14,7 @@ This demo demonstrates: - **Gazebo Harmonic simulation** with a realistic factory scene (conveyor belt, work table, storage, lighting) - **Continuous pick-and-place** loop as a realistic manipulation workload - **Manipulation fault monitoring** (planning failures, collision detection) -- **Fault snapshots** — environment state captured at fault time (joint states, diagnostics) +- **Fault snapshots** - environment state captured at fault time (joint states, diagnostics) - **SOVD-compliant REST API** with Areas → Components → Apps → Functions hierarchy - **Manifest-based entity discovery** (hybrid mode with runtime enrichment) - **2 fault injection scenarios** with visible Gazebo models and one-click scripts @@ -25,7 +25,7 @@ This demo demonstrates: - Docker and docker-compose - `curl` and `jq` installed on the host (required for host-side scripts) - X11 display server (for Gazebo GUI) or `--headless` mode -- (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) — recommended for smooth Gazebo rendering +- (Optional) NVIDIA GPU + [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) - recommended for smooth Gazebo rendering - ~7 GB disk space for Docker image ## Quick Start @@ -129,37 +129,37 @@ docker exec -it moveit_medkit_demo bash # Shell into container ``` Areas -├── manipulation/ — Robot arm and gripper hardware +├── manipulation/ - Robot arm and gripper hardware │ Components -│ ├── panda-arm — 7-DOF Franka Emika Panda +│ ├── panda-arm - 7-DOF Franka Emika Panda │ │ Apps: joint-state-broadcaster, panda-arm-controller, robot-state-publisher -│ └── panda-gripper — 2-finger parallel gripper +│ └── panda-gripper - 2-finger parallel gripper │ Apps: panda-hand-controller │ -├── planning/ — MoveIt 2 motion planning stack +├── planning/ - MoveIt 2 motion planning stack │ Components -│ ├── moveit-planning — OMPL planning pipeline +│ ├── moveit-planning - OMPL planning pipeline │ │ Apps: move-group -│ └── pick-place-loop — Pick-and-place demo node +│ └── pick-place-loop - Pick-and-place demo node │ Apps: pick-place-node │ -├── diagnostics/ — ros2_medkit gateway and fault management +├── diagnostics/ - ros2_medkit gateway and fault management │ Components -│ ├── gateway — REST API +│ ├── gateway - REST API │ │ Apps: medkit-gateway -│ └── fault-manager — Fault aggregation +│ └── fault-manager - Fault aggregation │ Apps: medkit-fault-manager │ -└── bridge/ — Legacy diagnostics bridge +└── bridge/ - Legacy diagnostics bridge Components └── diagnostic-bridge Apps: diagnostic-bridge-app, manipulation-monitor Functions -├── pick-and-place — Pick objects and place at target positions -├── motion-planning — Plan collision-free motion trajectories -├── gripper-control — Open and close the Panda gripper -└── fault-management — Collect and expose faults via SOVD API +├── pick-and-place - Pick objects and place at target positions +├── motion-planning - Plan collision-free motion trajectories +├── gripper-control - Open and close the Panda gripper +└── fault-management - Collect and expose faults via SOVD API ``` ## REST API Examples @@ -223,8 +223,8 @@ curl http://localhost:8080/api/v1/apps/manipulation-monitor/faults/MOTION_PLANNI ``` Captured topics (background capture, always available): -- `/joint_states` — Current joint positions at fault time -- `/diagnostics` — Active diagnostics messages +- `/joint_states` - Current joint positions at fault time +- `/diagnostics` - Active diagnostics messages ### Modify Configurations via REST API @@ -294,11 +294,13 @@ The gateway supports condition-based triggers that fire when specific events occ # Terminal 1: Start the demo ./run-demo.sh -# Terminal 2: Create fault trigger and watch for events -./setup-triggers.sh # Creates OnChange trigger on moveit-planning faults -./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) +# Terminal 2: Create the fault trigger +./setup-triggers.sh -# Terminal 3: Inject a fault - trigger fires! +# Terminal 3: Watch for trigger events (blocking - Ctrl+C to stop) +./watch-triggers.sh + +# Terminal 2: Inject a fault - the trigger fires in Terminal 3! ./inject-planning-failure.sh ``` @@ -349,7 +351,7 @@ Blocks the robot's path with a large collision wall (visible as orange wall in G | Code | Severity | Description | |------|----------|-------------| -| `MOTION_PLANNING_FAILED` | ERROR | MoveGroup goal ABORTED — no collision-free path | +| `MOTION_PLANNING_FAILED` | ERROR | MoveGroup goal ABORTED - no collision-free path | ### 2. Collision Detection @@ -397,16 +399,18 @@ Connect it to the gateway at `http://localhost:8080` to browse: | Script | Description | |--------|-------------| -| `run-demo.sh` | **Start the demo** — build and launch the Docker container | +| `run-demo.sh` | **Start the demo** - build and launch the Docker container | | `stop-demo.sh` | Stop demo containers | -| `move-arm.sh` | **Interactive arm controller** — move to preset positions | +| `move-arm.sh` | **Interactive arm controller** - move to preset positions | | `check-entities.sh` | Explore the full SOVD entity hierarchy with sample data | | `check-faults.sh` | View active faults with severity summary | -| `inject-planning-failure.sh` | Scripts API wrapper — inject planning failure | -| `inject-collision.sh` | Scripts API wrapper — inject collision obstacle | -| `restore-normal.sh` | Scripts API wrapper — restore normal operation | -| `arm-self-test.sh` | Scripts API wrapper — run arm self-test | -| `planning-benchmark.sh` | Scripts API wrapper — run planning benchmark | +| `inject-planning-failure.sh` | Scripts API wrapper - inject planning failure | +| `inject-collision.sh` | Scripts API wrapper - inject collision obstacle | +| `restore-normal.sh` | Scripts API wrapper - restore normal operation | +| `arm-self-test.sh` | Scripts API wrapper - run arm self-test | +| `planning-benchmark.sh` | Scripts API wrapper - run planning benchmark | +| `setup-triggers.sh` | Create OnChange fault trigger | +| `watch-triggers.sh` | Watch trigger events via SSE stream | Scripts API wrappers require `curl` and `jq` on the host and call the gateway REST endpoint directly - no `docker exec` needed. diff --git a/demos/sensor_diagnostics/README.md b/demos/sensor_diagnostics/README.md index 2aea857..2554b5e 100644 --- a/demos/sensor_diagnostics/README.md +++ b/demos/sensor_diagnostics/README.md @@ -194,11 +194,13 @@ The gateway supports condition-based triggers that fire when specific events occ # Terminal 1: Start the demo ./run-demo.sh -# Terminal 2: Create fault trigger and watch for events -./setup-triggers.sh # Creates OnChange trigger on compute-unit faults -./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) +# Terminal 2: Create the fault trigger +./setup-triggers.sh -# Terminal 3: Inject a fault - trigger fires! +# Terminal 3: Watch for trigger events (blocking - Ctrl+C to stop) +./watch-triggers.sh + +# Terminal 2: Inject a fault - the trigger fires in Terminal 3! ./inject-nan.sh ``` @@ -310,6 +312,8 @@ curl http://localhost:8080/api/v1/faults | jq | `inject-nan.sh` | Inject NaN values | | `inject-drift.sh` | Enable sensor drift | | `restore-normal.sh` | Clear all faults | +| `setup-triggers.sh` | Create OnChange fault trigger | +| `watch-triggers.sh` | Watch trigger events via SSE stream | > **Note:** All diagnostic scripts (`inject-*.sh`, `restore-normal.sh`, `run-diagnostics.sh`, `inject-fault-scenario.sh`) are also available via the [Scripts API](#scripts-api) - callable as REST endpoints without requiring the host-side scripts. diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index b4e9ca3..22902ee 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -410,11 +410,13 @@ The gateway supports condition-based triggers that fire when specific events occ # Terminal 1: Start the demo ./run-demo.sh -# Terminal 2: Create fault trigger and watch for events -./setup-triggers.sh # Creates OnChange trigger on nav2-stack faults -./watch-triggers.sh # Connects to SSE stream (Ctrl+C to stop) +# Terminal 2: Create the fault trigger +./setup-triggers.sh -# Terminal 3: Inject a fault - trigger fires! +# Terminal 3: Watch for trigger events (blocking - Ctrl+C to stop) +./watch-triggers.sh + +# Terminal 2: Inject a fault - the trigger fires in Terminal 3! ./inject-nav-failure.sh ``` @@ -594,6 +596,8 @@ demos/turtlebot3_integration/ | `inject-nav-failure.sh` | Inject navigation failure (unreachable goal) | | `inject-localization-failure.sh` | Inject localization failure (AMCL reset) | | `restore-normal.sh` | Restore normal operation and clear faults | +| `setup-triggers.sh` | Create OnChange fault trigger | +| `watch-triggers.sh` | Watch trigger events via SSE stream | > **Note:** The inject, restore, and diagnostic scripts are also available via the [Scripts API](#scripts-api) - callable as REST endpoints without requiring the host-side scripts. From be6bbe68ea4755e205326fc7d894d9b850851d11 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Sun, 22 Mar 2026 23:05:07 +0100 Subject: [PATCH 12/16] fix: use correct entity IDs for fault triggers (match reporting_sources) Triggers must target the entity whose ROS node name appears in reporting_sources[0] of the FaultEvent. The TriggerFaultSubscriber extracts entity_id from this field. - sensor_diagnostics: apps/diagnostic_bridge (was components/compute-unit) - turtlebot3: apps/anomaly_detector (was components/nav2-stack) - moveit: apps/manipulation_monitor (was components/moveit-planning) Verified end-to-end: setup-triggers -> watch-triggers -> inject-nan produces SSE events with fault payloads. --- demos/moveit_pick_place/README.md | 16 ++++++++-------- demos/moveit_pick_place/setup-triggers.sh | 6 +++--- demos/moveit_pick_place/watch-triggers.sh | 4 ++-- demos/sensor_diagnostics/README.md | 14 +++++++------- demos/sensor_diagnostics/setup-triggers.sh | 6 +++--- demos/sensor_diagnostics/watch-triggers.sh | 4 ++-- demos/turtlebot3_integration/README.md | 16 ++++++++-------- demos/turtlebot3_integration/setup-triggers.sh | 6 +++--- demos/turtlebot3_integration/watch-triggers.sh | 4 ++-- 9 files changed, 38 insertions(+), 38 deletions(-) diff --git a/demos/moveit_pick_place/README.md b/demos/moveit_pick_place/README.md index 46343d2..9a300b7 100644 --- a/demos/moveit_pick_place/README.md +++ b/demos/moveit_pick_place/README.md @@ -286,7 +286,7 @@ The host-side wrapper scripts (`./inject-collision.sh`, etc.) call the Scripts A ## Triggers (Condition-Based Alerts) -The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on planning failure faults on the moveit-planning component. +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on planning failure faults reported by the manipulation monitor. ### Setup @@ -306,8 +306,8 @@ The gateway supports condition-based triggers that fire when specific events occ ### How It Works -1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/moveit-planning/triggers`: - - **Resource:** `/api/v1/components/moveit-planning/faults` (watches fault collection) +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/apps/manipulation_monitor/triggers`: + - **Resource:** `/api/v1/apps/manipulation_monitor/faults` (watches fault collection) - **Condition:** `OnChange` (fires on any new or updated fault) - **Multishot:** `true` (fires repeatedly, not just once) - **Lifetime:** 3600 seconds (auto-expires after 1 hour) @@ -318,23 +318,23 @@ The gateway supports condition-based triggers that fire when specific events occ ```bash # Create a trigger -curl -X POST http://localhost:8080/api/v1/components/moveit-planning/triggers \ +curl -X POST http://localhost:8080/api/v1/apps/manipulation_monitor/triggers \ -H "Content-Type: application/json" \ -d '{ - "resource": "/api/v1/components/moveit-planning/faults", + "resource": "/api/v1/apps/manipulation_monitor/faults", "trigger_condition": {"condition_type": "OnChange"}, "multishot": true, "lifetime": 3600 }' | jq # List triggers -curl http://localhost:8080/api/v1/components/moveit-planning/triggers | jq +curl http://localhost:8080/api/v1/apps/manipulation_monitor/triggers | jq # Watch events (replace TRIGGER_ID) -curl -N http://localhost:8080/api/v1/components/moveit-planning/triggers/TRIGGER_ID/events +curl -N http://localhost:8080/api/v1/apps/manipulation_monitor/triggers/TRIGGER_ID/events # Delete a trigger -curl -X DELETE http://localhost:8080/api/v1/components/moveit-planning/triggers/TRIGGER_ID +curl -X DELETE http://localhost:8080/api/v1/apps/manipulation_monitor/triggers/TRIGGER_ID ``` ## Fault Injection Scenarios diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh index 73fe819..5562f8a 100755 --- a/demos/moveit_pick_place/setup-triggers.sh +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -1,13 +1,13 @@ #!/bin/bash # Create fault-monitoring trigger for moveit pick-and-place demo -# Alerts on planning failure faults on the moveit-planning component +# Alerts on planning failure faults reported by the manipulation monitor set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="moveit-planning" +ENTITY_TYPE="apps" +ENTITY_ID="manipulation_monitor" # Check for existing active trigger existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") diff --git a/demos/moveit_pick_place/watch-triggers.sh b/demos/moveit_pick_place/watch-triggers.sh index 7045028..9eaa6eb 100755 --- a/demos/moveit_pick_place/watch-triggers.sh +++ b/demos/moveit_pick_place/watch-triggers.sh @@ -6,8 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="moveit-planning" +ENTITY_TYPE="apps" +ENTITY_ID="manipulation_monitor" trigger_id="${1:-}" diff --git a/demos/sensor_diagnostics/README.md b/demos/sensor_diagnostics/README.md index 2554b5e..d89a033 100644 --- a/demos/sensor_diagnostics/README.md +++ b/demos/sensor_diagnostics/README.md @@ -206,8 +206,8 @@ The gateway supports condition-based triggers that fire when specific events occ ### How It Works -1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/compute-unit/triggers`: - - **Resource:** `/api/v1/components/compute-unit/faults` (watches fault collection) +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/apps/diagnostic_bridge/triggers`: + - **Resource:** `/api/v1/apps/diagnostic_bridge/faults` (watches fault collection) - **Condition:** `OnChange` (fires on any new or updated fault) - **Multishot:** `true` (fires repeatedly, not just once) - **Lifetime:** 3600 seconds (auto-expires after 1 hour) @@ -218,23 +218,23 @@ The gateway supports condition-based triggers that fire when specific events occ ```bash # Create a trigger -curl -X POST http://localhost:8080/api/v1/components/compute-unit/triggers \ +curl -X POST http://localhost:8080/api/v1/apps/diagnostic_bridge/triggers \ -H "Content-Type: application/json" \ -d '{ - "resource": "/api/v1/components/compute-unit/faults", + "resource": "/api/v1/apps/diagnostic_bridge/faults", "trigger_condition": {"condition_type": "OnChange"}, "multishot": true, "lifetime": 3600 }' | jq # List triggers -curl http://localhost:8080/api/v1/components/compute-unit/triggers | jq +curl http://localhost:8080/api/v1/apps/diagnostic_bridge/triggers | jq # Watch events (replace TRIGGER_ID) -curl -N http://localhost:8080/api/v1/components/compute-unit/triggers/TRIGGER_ID/events +curl -N http://localhost:8080/api/v1/apps/diagnostic_bridge/triggers/TRIGGER_ID/events # Delete a trigger -curl -X DELETE http://localhost:8080/api/v1/components/compute-unit/triggers/TRIGGER_ID +curl -X DELETE http://localhost:8080/api/v1/apps/diagnostic_bridge/triggers/TRIGGER_ID ``` ## API Examples diff --git a/demos/sensor_diagnostics/setup-triggers.sh b/demos/sensor_diagnostics/setup-triggers.sh index 48765ed..0e529f0 100755 --- a/demos/sensor_diagnostics/setup-triggers.sh +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -1,13 +1,13 @@ #!/bin/bash # Create fault-monitoring trigger for sensor diagnostics demo -# Alerts on any new fault reported on the compute-unit component +# Alerts on any new fault reported via the diagnostic bridge set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="compute-unit" +ENTITY_TYPE="apps" +ENTITY_ID="diagnostic_bridge" # Check for existing active trigger existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") diff --git a/demos/sensor_diagnostics/watch-triggers.sh b/demos/sensor_diagnostics/watch-triggers.sh index 01cd26a..d4e5ddb 100755 --- a/demos/sensor_diagnostics/watch-triggers.sh +++ b/demos/sensor_diagnostics/watch-triggers.sh @@ -6,8 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="compute-unit" +ENTITY_TYPE="apps" +ENTITY_ID="diagnostic_bridge" trigger_id="${1:-}" diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 22902ee..80dcaee 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -402,7 +402,7 @@ GATEWAY_URL=http://192.168.1.10:8080 ./inject-nav-failure.sh ## Triggers (Condition-Based Alerts) -The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on navigation failure faults on the nav2-stack component. +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on navigation failure faults reported by the anomaly detector. ### Setup @@ -422,8 +422,8 @@ The gateway supports condition-based triggers that fire when specific events occ ### How It Works -1. `setup-triggers.sh` creates a trigger via `POST /api/v1/components/nav2-stack/triggers`: - - **Resource:** `/api/v1/components/nav2-stack/faults` (watches fault collection) +1. `setup-triggers.sh` creates a trigger via `POST /api/v1/apps/anomaly_detector/triggers`: + - **Resource:** `/api/v1/apps/anomaly_detector/faults` (watches fault collection) - **Condition:** `OnChange` (fires on any new or updated fault) - **Multishot:** `true` (fires repeatedly, not just once) - **Lifetime:** 3600 seconds (auto-expires after 1 hour) @@ -434,23 +434,23 @@ The gateway supports condition-based triggers that fire when specific events occ ```bash # Create a trigger -curl -X POST http://localhost:8080/api/v1/components/nav2-stack/triggers \ +curl -X POST http://localhost:8080/api/v1/apps/anomaly_detector/triggers \ -H "Content-Type: application/json" \ -d '{ - "resource": "/api/v1/components/nav2-stack/faults", + "resource": "/api/v1/apps/anomaly_detector/faults", "trigger_condition": {"condition_type": "OnChange"}, "multishot": true, "lifetime": 3600 }' | jq # List triggers -curl http://localhost:8080/api/v1/components/nav2-stack/triggers | jq +curl http://localhost:8080/api/v1/apps/anomaly_detector/triggers | jq # Watch events (replace TRIGGER_ID) -curl -N http://localhost:8080/api/v1/components/nav2-stack/triggers/TRIGGER_ID/events +curl -N http://localhost:8080/api/v1/apps/anomaly_detector/triggers/TRIGGER_ID/events # Delete a trigger -curl -X DELETE http://localhost:8080/api/v1/components/nav2-stack/triggers/TRIGGER_ID +curl -X DELETE http://localhost:8080/api/v1/apps/anomaly_detector/triggers/TRIGGER_ID ``` ## Fault Injection Scenarios diff --git a/demos/turtlebot3_integration/setup-triggers.sh b/demos/turtlebot3_integration/setup-triggers.sh index d076afc..13bea9a 100755 --- a/demos/turtlebot3_integration/setup-triggers.sh +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -1,13 +1,13 @@ #!/bin/bash # Create fault-monitoring trigger for turtlebot3 integration demo -# Alerts on navigation failure faults on the nav2-stack component +# Alerts on navigation failure faults reported by the anomaly detector set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="nav2-stack" +ENTITY_TYPE="apps" +ENTITY_ID="anomaly_detector" # Check for existing active trigger existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") diff --git a/demos/turtlebot3_integration/watch-triggers.sh b/demos/turtlebot3_integration/watch-triggers.sh index 2ac4254..7588178 100755 --- a/demos/turtlebot3_integration/watch-triggers.sh +++ b/demos/turtlebot3_integration/watch-triggers.sh @@ -6,8 +6,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" -ENTITY_TYPE="components" -ENTITY_ID="nav2-stack" +ENTITY_TYPE="apps" +ENTITY_ID="anomaly_detector" trigger_id="${1:-}" From 3b3c53f0fe0c4cca54cb4c2fc6eb2115674efabb Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Mon, 23 Mar 2026 17:10:47 +0100 Subject: [PATCH 13/16] fix: clarify trigger descriptions (OnChange fires on any fault, not just specific ones) --- demos/moveit_pick_place/README.md | 2 +- demos/moveit_pick_place/setup-triggers.sh | 3 ++- demos/sensor_diagnostics/setup-triggers.sh | 1 + demos/turtlebot3_integration/README.md | 2 +- demos/turtlebot3_integration/setup-triggers.sh | 3 ++- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/demos/moveit_pick_place/README.md b/demos/moveit_pick_place/README.md index 9a300b7..61aeab8 100644 --- a/demos/moveit_pick_place/README.md +++ b/demos/moveit_pick_place/README.md @@ -286,7 +286,7 @@ The host-side wrapper scripts (`./inject-collision.sh`, etc.) call the Scripts A ## Triggers (Condition-Based Alerts) -The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on planning failure faults reported by the manipulation monitor. +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on any new or updated faults reported by the manipulation monitor (including planning failures and collisions). ### Setup diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh index 5562f8a..3a8ed57 100755 --- a/demos/moveit_pick_place/setup-triggers.sh +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -1,12 +1,13 @@ #!/bin/bash # Create fault-monitoring trigger for moveit pick-and-place demo -# Alerts on planning failure faults reported by the manipulation monitor +# Alerts on any fault change reported by the manipulation monitor set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="apps" +# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent ENTITY_ID="manipulation_monitor" # Check for existing active trigger diff --git a/demos/sensor_diagnostics/setup-triggers.sh b/demos/sensor_diagnostics/setup-triggers.sh index 0e529f0..74824a6 100755 --- a/demos/sensor_diagnostics/setup-triggers.sh +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -7,6 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="apps" +# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent ENTITY_ID="diagnostic_bridge" # Check for existing active trigger diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md index 80dcaee..2487961 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -402,7 +402,7 @@ GATEWAY_URL=http://192.168.1.10:8080 ./inject-nav-failure.sh ## Triggers (Condition-Based Alerts) -The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on navigation failure faults reported by the anomaly detector. +The gateway supports condition-based triggers that fire when specific events occur, delivering notifications via Server-Sent Events (SSE). This demo creates a fault-monitoring trigger that alerts on any new or updated faults reported by the anomaly detector (including navigation failures). ### Setup diff --git a/demos/turtlebot3_integration/setup-triggers.sh b/demos/turtlebot3_integration/setup-triggers.sh index 13bea9a..483a597 100755 --- a/demos/turtlebot3_integration/setup-triggers.sh +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -1,12 +1,13 @@ #!/bin/bash # Create fault-monitoring trigger for turtlebot3 integration demo -# Alerts on navigation failure faults reported by the anomaly detector +# Alerts on any fault change reported by the anomaly detector set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "${SCRIPT_DIR}/../../lib/triggers-api.sh" ENTITY_TYPE="apps" +# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent ENTITY_ID="anomaly_detector" # Check for existing active trigger From 688710768d32b28152a8726c0fd9be411f85464a Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Mon, 23 Mar 2026 23:02:36 +0100 Subject: [PATCH 14/16] test: add trigger lifecycle to sensor_diagnostics smoke test --- tests/smoke_test.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/smoke_test.sh b/tests/smoke_test.sh index b4f61fa..3660400 100755 --- a/tests/smoke_test.sh +++ b/tests/smoke_test.sh @@ -114,6 +114,69 @@ if [ "$cleared" = false ]; then fail "LIDAR_SIM fault cleared after cleanup" "fault still present after 5s" fi +section "Triggers" + +# Create a trigger on diagnostic_bridge faults +echo " Creating OnChange fault trigger..." +TRIGGER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_BASE}/apps/diagnostic_bridge/triggers" \ + -H "Content-Type: application/json" \ + -d '{"resource":"/api/v1/apps/diagnostic_bridge/faults","trigger_condition":{"condition_type":"OnChange"},"multishot":true,"lifetime":60}' 2>/dev/null) || true + +TRIGGER_HTTP=$(echo "$TRIGGER_RESPONSE" | tail -1) +TRIGGER_BODY=$(echo "$TRIGGER_RESPONSE" | sed '$d') + +if [ "$TRIGGER_HTTP" = "201" ]; then + pass "POST /apps/diagnostic_bridge/triggers returns 201" +else + fail "POST /apps/diagnostic_bridge/triggers returns 201" "got HTTP $TRIGGER_HTTP" +fi + +TRIGGER_ID=$(echo "$TRIGGER_BODY" | jq -r '.id') +if [ -n "$TRIGGER_ID" ] && [ "$TRIGGER_ID" != "null" ]; then + pass "trigger response contains valid id" +else + fail "trigger response contains valid id" "id is null or empty" +fi + +TRIGGER_STATUS=$(echo "$TRIGGER_BODY" | jq -r '.status') +if [ "$TRIGGER_STATUS" = "active" ]; then + pass "trigger status is 'active'" +else + fail "trigger status is 'active'" "got '$TRIGGER_STATUS'" +fi + +# List triggers - verify it appears +if api_get "/apps/diagnostic_bridge/triggers"; then + if echo "$RESPONSE" | jq -e --arg id "$TRIGGER_ID" '.items[] | select(.id == $id)' > /dev/null 2>&1; then + pass "GET /apps/diagnostic_bridge/triggers lists created trigger" + else + fail "GET /apps/diagnostic_bridge/triggers lists created trigger" "trigger $TRIGGER_ID not found" + fi +else + fail "GET /apps/diagnostic_bridge/triggers returns 200" "unexpected status code" +fi + +# Delete trigger +TRIGGER_DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ + "${API_BASE}/apps/diagnostic_bridge/triggers/${TRIGGER_ID}" 2>/dev/null) || true + +if [ "$TRIGGER_DELETE_STATUS" = "204" ]; then + pass "DELETE /apps/diagnostic_bridge/triggers/$TRIGGER_ID returns 204" +else + fail "DELETE /apps/diagnostic_bridge/triggers/$TRIGGER_ID returns 204" "got HTTP $TRIGGER_DELETE_STATUS" +fi + +# Verify trigger is gone +if api_get "/apps/diagnostic_bridge/triggers"; then + if ! echo "$RESPONSE" | jq -e --arg id "$TRIGGER_ID" '.items[] | select(.id == $id)' > /dev/null 2>&1; then + pass "trigger no longer listed after deletion" + else + fail "trigger no longer listed after deletion" "still found in list" + fi +else + fail "GET /apps/diagnostic_bridge/triggers returns 200 after delete" "unexpected status code" +fi + # --- Summary --- print_summary From 2393e87c4c1aec97aab6f9250a1d71344fe641d5 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Mon, 23 Mar 2026 23:10:01 +0100 Subject: [PATCH 15/16] refactor: address review feedback - dedup scripts, add validation, fix curl error handling - Extract common setup/watch logic to lib/setup-trigger.sh and lib/watch-trigger.sh; per-demo scripts are now thin wrappers setting ENTITY_TYPE/ENTITY_ID/INJECT_HINT - Add trigger_id validation in delete_trigger and watch_trigger_events - Fix curl error handling in create_fault_trigger: capture failure explicitly instead of swallowing with || true --- demos/moveit_pick_place/setup-triggers.sh | 47 ++---------------- demos/moveit_pick_place/watch-triggers.sh | 25 ++-------- demos/sensor_diagnostics/setup-triggers.sh | 47 ++---------------- demos/sensor_diagnostics/watch-triggers.sh | 25 ++-------- .../turtlebot3_integration/setup-triggers.sh | 47 ++---------------- .../turtlebot3_integration/watch-triggers.sh | 25 ++-------- lib/setup-trigger.sh | 48 +++++++++++++++++++ lib/triggers-api.sh | 17 ++++++- lib/watch-trigger.sh | 29 +++++++++++ 9 files changed, 116 insertions(+), 194 deletions(-) create mode 100644 lib/setup-trigger.sh create mode 100644 lib/watch-trigger.sh diff --git a/demos/moveit_pick_place/setup-triggers.sh b/demos/moveit_pick_place/setup-triggers.sh index 3a8ed57..06ff53e 100755 --- a/demos/moveit_pick_place/setup-triggers.sh +++ b/demos/moveit_pick_place/setup-triggers.sh @@ -1,46 +1,9 @@ #!/bin/bash # Create fault-monitoring trigger for moveit pick-and-place demo # Alerts on any fault change reported by the manipulation monitor -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" +export ENTITY_TYPE="apps" # Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -ENTITY_ID="manipulation_monitor" - -# Check for existing active trigger -existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") -if [ -n "$existing" ]; then - echo "Active trigger already exists: ${existing}" - echo "Run ./watch-triggers.sh to connect, or delete it first:" - echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" - exit 0 -fi - -echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." -echo "" - -result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") -trigger_id=$(echo "$result" | jq -r '.id') - -if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then - echo "Failed to parse trigger response." >&2 - echo "$result" >&2 - exit 1 -fi - -status=$(echo "$result" | jq -r '.status') -event_source=$(echo "$result" | jq -r '.event_source') - -echo "Trigger created successfully!" -echo " ID: ${trigger_id}" -echo " Status: ${status}" -echo " Events: ${GATEWAY_URL}${event_source}" -echo "" -echo "To watch for events:" -echo " ./watch-triggers.sh ${trigger_id}" -echo "" -echo "Then inject a fault in another terminal:" -echo " ./inject-planning-failure.sh" +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 9eaa6eb..4cae6e7 100755 --- a/demos/moveit_pick_place/watch-triggers.sh +++ b/demos/moveit_pick_place/watch-triggers.sh @@ -1,26 +1,7 @@ #!/bin/bash # Watch trigger events for moveit pick-and-place demo # Connects to SSE stream and prints fault events in real time -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +export ENTITY_TYPE="apps" +export ENTITY_ID="manipulation_monitor" # shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" -ENTITY_ID="manipulation_monitor" - -trigger_id="${1:-}" - -if [ -z "$trigger_id" ]; then - # Auto-detect: find first active trigger - trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") - if [ -z "$trigger_id" ]; then - echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." - echo "Create one first: ./setup-triggers.sh" - exit 1 - fi - echo "Found active trigger: ${trigger_id}" - echo "" -fi - -watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" +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 74824a6..f7dcbaf 100755 --- a/demos/sensor_diagnostics/setup-triggers.sh +++ b/demos/sensor_diagnostics/setup-triggers.sh @@ -1,46 +1,9 @@ #!/bin/bash # Create fault-monitoring trigger for sensor diagnostics demo # Alerts on any new fault reported via the diagnostic bridge -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" +export ENTITY_TYPE="apps" # Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -ENTITY_ID="diagnostic_bridge" - -# Check for existing active trigger -existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") -if [ -n "$existing" ]; then - echo "Active trigger already exists: ${existing}" - echo "Run ./watch-triggers.sh to connect, or delete it first:" - echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" - exit 0 -fi - -echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." -echo "" - -result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") -trigger_id=$(echo "$result" | jq -r '.id') - -if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then - echo "Failed to parse trigger response." >&2 - echo "$result" >&2 - exit 1 -fi - -status=$(echo "$result" | jq -r '.status') -event_source=$(echo "$result" | jq -r '.event_source') - -echo "Trigger created successfully!" -echo " ID: ${trigger_id}" -echo " Status: ${status}" -echo " Events: ${GATEWAY_URL}${event_source}" -echo "" -echo "To watch for events:" -echo " ./watch-triggers.sh ${trigger_id}" -echo "" -echo "Then inject a fault in another terminal:" -echo " ./inject-nan.sh" +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 d4e5ddb..298f3b4 100755 --- a/demos/sensor_diagnostics/watch-triggers.sh +++ b/demos/sensor_diagnostics/watch-triggers.sh @@ -1,26 +1,7 @@ #!/bin/bash # Watch trigger events for sensor diagnostics demo # Connects to SSE stream and prints fault events in real time -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +export ENTITY_TYPE="apps" +export ENTITY_ID="diagnostic_bridge" # shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" -ENTITY_ID="diagnostic_bridge" - -trigger_id="${1:-}" - -if [ -z "$trigger_id" ]; then - # Auto-detect: find first active trigger - trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") - if [ -z "$trigger_id" ]; then - echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." - echo "Create one first: ./setup-triggers.sh" - exit 1 - fi - echo "Found active trigger: ${trigger_id}" - echo "" -fi - -watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" +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 483a597..8092c13 100755 --- a/demos/turtlebot3_integration/setup-triggers.sh +++ b/demos/turtlebot3_integration/setup-triggers.sh @@ -1,46 +1,9 @@ #!/bin/bash # Create fault-monitoring trigger for turtlebot3 integration demo # Alerts on any fault change reported by the anomaly detector -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" +export ENTITY_TYPE="apps" # Uses ROS node name (underscore) - must match reporting_sources in FaultEvent -ENTITY_ID="anomaly_detector" - -# Check for existing active trigger -existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") -if [ -n "$existing" ]; then - echo "Active trigger already exists: ${existing}" - echo "Run ./watch-triggers.sh to connect, or delete it first:" - echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" - exit 0 -fi - -echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." -echo "" - -result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") -trigger_id=$(echo "$result" | jq -r '.id') - -if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then - echo "Failed to parse trigger response." >&2 - echo "$result" >&2 - exit 1 -fi - -status=$(echo "$result" | jq -r '.status') -event_source=$(echo "$result" | jq -r '.event_source') - -echo "Trigger created successfully!" -echo " ID: ${trigger_id}" -echo " Status: ${status}" -echo " Events: ${GATEWAY_URL}${event_source}" -echo "" -echo "To watch for events:" -echo " ./watch-triggers.sh ${trigger_id}" -echo "" -echo "Then inject a fault in another terminal:" -echo " ./inject-nav-failure.sh" +export ENTITY_ID="anomaly_detector" +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 7588178..6f96330 100755 --- a/demos/turtlebot3_integration/watch-triggers.sh +++ b/demos/turtlebot3_integration/watch-triggers.sh @@ -1,26 +1,7 @@ #!/bin/bash # Watch trigger events for turtlebot3 integration demo # Connects to SSE stream and prints fault events in real time -set -eu -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +export ENTITY_TYPE="apps" +export ENTITY_ID="anomaly_detector" # shellcheck disable=SC1091 -source "${SCRIPT_DIR}/../../lib/triggers-api.sh" - -ENTITY_TYPE="apps" -ENTITY_ID="anomaly_detector" - -trigger_id="${1:-}" - -if [ -z "$trigger_id" ]; then - # Auto-detect: find first active trigger - trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") - if [ -z "$trigger_id" ]; then - echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." - echo "Create one first: ./setup-triggers.sh" - exit 1 - fi - echo "Found active trigger: ${trigger_id}" - echo "" -fi - -watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" +source "$(cd "$(dirname "$0")" && pwd)/../../lib/watch-trigger.sh" "$@" diff --git a/lib/setup-trigger.sh b/lib/setup-trigger.sh new file mode 100644 index 0000000..83c937a --- /dev/null +++ b/lib/setup-trigger.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Generic trigger setup - called by per-demo setup-triggers.sh wrappers +# Requires ENTITY_TYPE, ENTITY_ID, and INJECT_HINT to be set before sourcing +set -eu + +if [ -z "${ENTITY_TYPE:-}" ] || [ -z "${ENTITY_ID:-}" ]; then + echo "ENTITY_TYPE and ENTITY_ID must be set before sourcing this script." >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/triggers-api.sh" + +# Check for existing active trigger +existing=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") +if [ -n "$existing" ]; then + echo "Active trigger already exists: ${existing}" + echo "Run ./watch-triggers.sh to connect, or delete it first:" + echo " curl -X DELETE ${GATEWAY_URL}/api/v1/${ENTITY_TYPE}/${ENTITY_ID}/triggers/${existing}" + exit 0 +fi + +echo "Setting up fault trigger for ${ENTITY_TYPE}/${ENTITY_ID}..." +echo "" + +result=$(create_fault_trigger "$ENTITY_TYPE" "$ENTITY_ID") +trigger_id=$(echo "$result" | jq -r '.id') + +if [ -z "$trigger_id" ] || [ "$trigger_id" = "null" ]; then + echo "Failed to parse trigger response." >&2 + echo "$result" >&2 + exit 1 +fi + +status=$(echo "$result" | jq -r '.status') +event_source=$(echo "$result" | jq -r '.event_source') + +echo "Trigger created successfully!" +echo " ID: ${trigger_id}" +echo " Status: ${status}" +echo " Events: ${GATEWAY_URL}${event_source}" +echo "" +echo "To watch for events:" +echo " ./watch-triggers.sh ${trigger_id}" +echo "" +echo "Then inject a fault in another terminal:" +echo " ${INJECT_HINT:-./inject-nan.sh}" diff --git a/lib/triggers-api.sh b/lib/triggers-api.sh index 3ec7334..0859d4e 100644 --- a/lib/triggers-api.sh +++ b/lib/triggers-api.sh @@ -50,10 +50,13 @@ create_fault_trigger() { local result local http_code - result=$(curl -s -w "\n%{http_code}" -X POST \ + if ! result=$(curl -s -w "\n%{http_code}" -X POST \ "${API_BASE}/${entity_type}/${entity_id}/triggers" \ -H "Content-Type: application/json" \ - -d "$body" 2>/dev/null) || true + -d "$body" 2>/dev/null); then + echo "Failed to reach gateway at ${GATEWAY_URL}." >&2 + return 1 + fi http_code=$(echo "$result" | tail -1) result=$(echo "$result" | sed '$d') @@ -85,6 +88,11 @@ delete_trigger() { local entity_id="$2" local trigger_id="$3" + if [[ ! "$trigger_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "Invalid trigger ID: '${trigger_id}'" >&2 + return 1 + fi + check_gateway local http_code @@ -106,6 +114,11 @@ watch_trigger_events() { local entity_id="$2" local trigger_id="$3" + if [[ ! "$trigger_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo "Invalid trigger ID: '${trigger_id}'" >&2 + return 1 + fi + check_gateway local url="${API_BASE}/${entity_type}/${entity_id}/triggers/${trigger_id}/events" diff --git a/lib/watch-trigger.sh b/lib/watch-trigger.sh new file mode 100644 index 0000000..57dfc1a --- /dev/null +++ b/lib/watch-trigger.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Generic trigger watcher - called by per-demo watch-triggers.sh wrappers +# Requires ENTITY_TYPE and ENTITY_ID to be set before sourcing +set -eu + +if [ -z "${ENTITY_TYPE:-}" ] || [ -z "${ENTITY_ID:-}" ]; then + echo "ENTITY_TYPE and ENTITY_ID must be set before sourcing this script." >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/triggers-api.sh" + +trigger_id="${1:-}" + +if [ -z "$trigger_id" ]; then + # Auto-detect: find first active trigger + trigger_id=$(find_active_trigger "$ENTITY_TYPE" "$ENTITY_ID") + if [ -z "$trigger_id" ]; then + echo "No active triggers found for ${ENTITY_TYPE}/${ENTITY_ID}." + echo "Create one first: ./setup-triggers.sh" + exit 1 + fi + echo "Found active trigger: ${trigger_id}" + echo "" +fi + +watch_trigger_events "$ENTITY_TYPE" "$ENTITY_ID" "$trigger_id" From 14205c50614e5a279186771d26bb1daa1befc8c8 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Tue, 24 Mar 2026 08:53:53 +0100 Subject: [PATCH 16/16] fix: use manifest entity ID (diagnostic-bridge) in smoke test triggers The runtime-discovered entity (diagnostic_bridge with underscore) may not exist in CI if the Docker image cache has an older gateway version. The manifest entity (diagnostic-bridge with hyphen) always exists. Trigger CRUD works on any entity - only SSE dispatch needs reporting_sources match. --- tests/smoke_test.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/smoke_test.sh b/tests/smoke_test.sh index 3660400..1c991ce 100755 --- a/tests/smoke_test.sh +++ b/tests/smoke_test.sh @@ -116,19 +116,19 @@ fi section "Triggers" -# Create a trigger on diagnostic_bridge faults +# Create a trigger on diagnostic-bridge faults echo " Creating OnChange fault trigger..." -TRIGGER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_BASE}/apps/diagnostic_bridge/triggers" \ +TRIGGER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_BASE}/apps/diagnostic-bridge/triggers" \ -H "Content-Type: application/json" \ - -d '{"resource":"/api/v1/apps/diagnostic_bridge/faults","trigger_condition":{"condition_type":"OnChange"},"multishot":true,"lifetime":60}' 2>/dev/null) || true + -d '{"resource":"/api/v1/apps/diagnostic-bridge/faults","trigger_condition":{"condition_type":"OnChange"},"multishot":true,"lifetime":60}' 2>/dev/null) || true TRIGGER_HTTP=$(echo "$TRIGGER_RESPONSE" | tail -1) TRIGGER_BODY=$(echo "$TRIGGER_RESPONSE" | sed '$d') if [ "$TRIGGER_HTTP" = "201" ]; then - pass "POST /apps/diagnostic_bridge/triggers returns 201" + pass "POST /apps/diagnostic-bridge/triggers returns 201" else - fail "POST /apps/diagnostic_bridge/triggers returns 201" "got HTTP $TRIGGER_HTTP" + fail "POST /apps/diagnostic-bridge/triggers returns 201" "got HTTP $TRIGGER_HTTP" fi TRIGGER_ID=$(echo "$TRIGGER_BODY" | jq -r '.id') @@ -146,35 +146,35 @@ else fi # List triggers - verify it appears -if api_get "/apps/diagnostic_bridge/triggers"; then +if api_get "/apps/diagnostic-bridge/triggers"; then if echo "$RESPONSE" | jq -e --arg id "$TRIGGER_ID" '.items[] | select(.id == $id)' > /dev/null 2>&1; then - pass "GET /apps/diagnostic_bridge/triggers lists created trigger" + pass "GET /apps/diagnostic-bridge/triggers lists created trigger" else - fail "GET /apps/diagnostic_bridge/triggers lists created trigger" "trigger $TRIGGER_ID not found" + fail "GET /apps/diagnostic-bridge/triggers lists created trigger" "trigger $TRIGGER_ID not found" fi else - fail "GET /apps/diagnostic_bridge/triggers returns 200" "unexpected status code" + fail "GET /apps/diagnostic-bridge/triggers returns 200" "unexpected status code" fi # Delete trigger TRIGGER_DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ - "${API_BASE}/apps/diagnostic_bridge/triggers/${TRIGGER_ID}" 2>/dev/null) || true + "${API_BASE}/apps/diagnostic-bridge/triggers/${TRIGGER_ID}" 2>/dev/null) || true if [ "$TRIGGER_DELETE_STATUS" = "204" ]; then - pass "DELETE /apps/diagnostic_bridge/triggers/$TRIGGER_ID returns 204" + pass "DELETE /apps/diagnostic-bridge/triggers/$TRIGGER_ID returns 204" else - fail "DELETE /apps/diagnostic_bridge/triggers/$TRIGGER_ID returns 204" "got HTTP $TRIGGER_DELETE_STATUS" + fail "DELETE /apps/diagnostic-bridge/triggers/$TRIGGER_ID returns 204" "got HTTP $TRIGGER_DELETE_STATUS" fi # Verify trigger is gone -if api_get "/apps/diagnostic_bridge/triggers"; then +if api_get "/apps/diagnostic-bridge/triggers"; then if ! echo "$RESPONSE" | jq -e --arg id "$TRIGGER_ID" '.items[] | select(.id == $id)' > /dev/null 2>&1; then pass "trigger no longer listed after deletion" else fail "trigger no longer listed after deletion" "still found in list" fi else - fail "GET /apps/diagnostic_bridge/triggers returns 200 after delete" "unexpected status code" + fail "GET /apps/diagnostic-bridge/triggers returns 200 after delete" "unexpected status code" fi # --- Summary ---