Skip to content

Commit 77f06a4

Browse files
committed
fix(demo): improve code quality, add CI and smoke test for multi-ECU demo
Code quality: - Fix copyright headers to SPDX format in all 10 source files - Fix namespace inconsistency: actuation files now use multi_ecu_demo - Add blocking sleep comments in path_planner and point_cloud_filter - Fix inject-cascade-failure.sh header comment (wrong parameters) - Use error counter pattern in inject-cascade-failure.sh Infrastructure: - Add check-demo.sh readiness script (polls health, shows entity counts) - Add smoke test (tests/smoke_test_multi_ecu.sh) using smoke_lib.sh - Add CI profile services to docker-compose.yml (headless, no web UI) - Add build-and-test-multi-ecu job to CI workflow - Share Docker image across ECU services (multi-ecu-demo:local) UX/correctness: - Add fault-clearing curl calls to all 3 restore scripts - Add gateway reachability check to host-side injection scripts - Use demo-specific container_name for web UI (multi_ecu_web_ui) - Quote ECU_LAUNCH in Dockerfile CMD - Update top-level README with new demo entry
1 parent a930f2c commit 77f06a4

24 files changed

Lines changed: 469 additions & 32 deletions

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,50 @@ jobs:
116116
if: always()
117117
working-directory: demos/moveit_pick_place
118118
run: docker compose --profile ci down
119+
120+
build-and-test-multi-ecu:
121+
needs: lint
122+
runs-on: ubuntu-24.04
123+
steps:
124+
- name: Show triggering source
125+
if: github.event_name == 'repository_dispatch'
126+
run: |
127+
SHA="${{ github.event.client_payload.sha }}"
128+
RUN_URL="${{ github.event.client_payload.run_url }}"
129+
echo "## Triggered by ros2_medkit" >> "$GITHUB_STEP_SUMMARY"
130+
echo "- Commit: \`${SHA:-unknown}\`" >> "$GITHUB_STEP_SUMMARY"
131+
if [ -n "$RUN_URL" ]; then
132+
echo "- Run: [View triggering run]($RUN_URL)" >> "$GITHUB_STEP_SUMMARY"
133+
else
134+
echo "- Run: (URL not provided)" >> "$GITHUB_STEP_SUMMARY"
135+
fi
136+
137+
- name: Checkout repository
138+
uses: actions/checkout@v4
139+
140+
- name: Build and start multi-ECU demo
141+
working-directory: demos/multi_ecu_aggregation
142+
run: docker compose --profile ci up -d --build perception-ecu-ci planning-ecu-ci actuation-ecu-ci
143+
144+
- name: Run smoke tests
145+
run: ./tests/smoke_test_multi_ecu.sh
146+
147+
- name: Show perception ECU logs on failure
148+
if: failure()
149+
working-directory: demos/multi_ecu_aggregation
150+
run: docker compose --profile ci logs perception-ecu-ci --tail=200
151+
152+
- name: Show planning ECU logs on failure
153+
if: failure()
154+
working-directory: demos/multi_ecu_aggregation
155+
run: docker compose --profile ci logs planning-ecu-ci --tail=200
156+
157+
- name: Show actuation ECU logs on failure
158+
if: failure()
159+
working-directory: demos/multi_ecu_aggregation
160+
run: docker compose --profile ci logs actuation-ecu-ci --tail=200
161+
162+
- name: Teardown
163+
if: always()
164+
working-directory: demos/multi_ecu_aggregation
165+
run: docker compose --profile ci down

README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ This repository contains example integrations and demos that show how ros2_medki
1414
can be used to add SOVD-compliant diagnostics and fault management to ROS 2-based robots and systems.
1515

1616
Each demo builds on real-world scenarios, progressing from simple sensor monitoring
17-
to complete mobile robot integration:
17+
to complete mobile robot integration and multi-ECU peer aggregation:
1818

19-
- **Sensor Diagnostics** — Lightweight demo focusing on data monitoring and fault injection
20-
- **TurtleBot3 Integration** — Full-featured demo with Nav2 navigation, showing entity hierarchy and real-time control
21-
- **MoveIt Pick-and-Place** — Panda 7-DOF arm manipulation with MoveIt 2, fault monitoring for planning, controllers, and joint limits
19+
- **Sensor Diagnostics** - Lightweight demo focusing on data monitoring and fault injection
20+
- **TurtleBot3 Integration** - Full-featured demo with Nav2 navigation, showing entity hierarchy and real-time control
21+
- **MoveIt Pick-and-Place** - Panda 7-DOF arm manipulation with MoveIt 2, fault monitoring for planning, controllers, and joint limits
22+
- **Multi-ECU Aggregation** - Peer aggregation with 3 ECUs (perception, planning, actuation), mDNS discovery, and cross-ECU functions
2223

2324
**Key Capabilities Demonstrated:**
2425

@@ -29,8 +30,11 @@ to complete mobile robot integration:
2930
- ✅ Fault management and injection
3031
- ✅ Manifest-based entity discovery
3132
- ✅ Legacy diagnostics bridge support
33+
- ✅ Multi-ECU peer aggregation
34+
- ✅ mDNS-based ECU discovery
35+
- ✅ Cross-ECU function grouping
3236

33-
Both demos support:
37+
All demos support:
3438

3539
- REST API access via SOVD protocol
3640
- Web UI for visualization ([ros2_medkit_web_ui](https://github.com/selfpatch/ros2_medkit_web_ui))
@@ -44,6 +48,7 @@ Both demos support:
4448
| [Sensor Diagnostics](demos/sensor_diagnostics/) | Lightweight sensor diagnostics demo (no Gazebo required) | Data monitoring, fault injection, dual fault reporting paths | ✅ Ready |
4549
| [TurtleBot3 Integration](demos/turtlebot3_integration/) | Full ros2_medkit integration with TurtleBot3 and Nav2 | SOVD-compliant API, manifest-based discovery, fault management | ✅ Ready |
4650
| [MoveIt Pick-and-Place](demos/moveit_pick_place/) | Panda 7-DOF arm with MoveIt 2 manipulation and ros2_medkit | Planning fault detection, controller monitoring, joint limits | ✅ Ready |
51+
| [Multi-ECU Aggregation](demos/multi_ecu_aggregation/) | Multi-ECU peer aggregation with 3 ECUs (perception, planning, actuation), mDNS discovery, cross-ECU functions | Peer aggregation, mDNS discovery, cross-ECU functions | ✅ Ready |
4752

4853
### Quick Start
4954

@@ -123,6 +128,28 @@ cd demos/moveit_pick_place
123128
- 5 fault injection scenarios with one-click scripts
124129
- SOVD-compliant REST API with rich entity hierarchy (4 areas, 7 components)
125130

131+
#### Multi-ECU Aggregation Demo (Advanced - Peer Aggregation)
132+
133+
Multi-ECU demo with 3 independent ECUs aggregated via mDNS discovery:
134+
135+
```bash
136+
cd demos/multi_ecu_aggregation
137+
./run-demo.sh
138+
./check-demo.sh # Verify all 3 ECUs are connected
139+
# Gateway at http://localhost:8080, Web UI at http://localhost:3000
140+
141+
# To stop
142+
./stop-demo.sh
143+
```
144+
145+
**Features:**
146+
147+
- 3 independent ECUs (perception, planning, actuation) each running ros2_medkit
148+
- Peer aggregation via mDNS-based automatic ECU discovery
149+
- Cross-ECU function grouping across the full system
150+
- Unified SOVD-compliant REST API spanning all ECUs
151+
- Web UI for browsing aggregated entity hierarchy
152+
126153
## Getting Started
127154

128155
### Prerequisites
@@ -144,7 +171,7 @@ Each demo has its own README with specific instructions. See above Quick Start,
144171
or follow the detailed README in each demo directory:
145172

146173
```bash
147-
cd demos/sensor_diagnostics # or turtlebot3_integration
174+
cd demos/sensor_diagnostics # or turtlebot3_integration, moveit_pick_place, multi_ecu_aggregation
148175
# Follow the README.md in that directory
149176
```
150177

@@ -184,7 +211,7 @@ Each demo has automated smoke tests that verify the gateway starts and the REST
184211
./tests/smoke_test_moveit.sh # MoveIt pick-and-place (discovery, data, operations, scripts, triggers, logs)
185212
```
186213

187-
CI runs all 3 demos in parallel - each job builds the Docker image, starts the container, and runs the smoke tests against it. See [CI workflow](.github/workflows/ci.yml).
214+
CI runs all 4 demos in parallel - each job builds the Docker image, starts the container, and runs the smoke tests against it. See [CI workflow](.github/workflows/ci.yml).
188215

189216
## Related Projects
190217

demos/multi_ecu_aggregation/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,4 @@ EXPOSE 8080
6868

6969
# ECU_LAUNCH env var selects which launch file to run
7070
ENV ECU_LAUNCH=perception.launch.py
71-
CMD ["bash", "-c", "mkdir -p /var/lib/ros2_medkit/rosbags && source /opt/ros/jazzy/setup.bash && source /root/demo_ws/install/setup.bash && ros2 launch multi_ecu_demo ${ECU_LAUNCH}"]
71+
CMD ["bash", "-c", "mkdir -p /var/lib/ros2_medkit/rosbags && source /opt/ros/jazzy/setup.bash && source /root/demo_ws/install/setup.bash && ros2 launch multi_ecu_demo \"${ECU_LAUNCH}\""]
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/bin/bash
2+
# Multi-ECU Aggregation Demo - Readiness Check
3+
# Waits for the gateway to become healthy, verifies peer connections, and prints entity counts
4+
5+
set -eu
6+
7+
GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
8+
API_BASE="${GATEWAY_URL}/api/v1"
9+
TIMEOUT=60
10+
11+
# Colors for output
12+
RED='\033[0;31m'
13+
GREEN='\033[0;32m'
14+
YELLOW='\033[0;33m'
15+
BLUE='\033[0;34m'
16+
BOLD='\033[1m'
17+
NC='\033[0m'
18+
19+
echo_success() { echo -e "${GREEN}ok${NC} $1"; }
20+
echo_fail() { echo -e "${RED}FAIL${NC} $1"; }
21+
echo_warn() { echo -e "${YELLOW}WARN${NC} $1"; }
22+
echo_info() { echo -e "${BLUE}--${NC} $1"; }
23+
24+
# Check dependencies
25+
for cmd in curl jq; do
26+
if ! command -v "$cmd" >/dev/null 2>&1; then
27+
echo_fail "Required tool '$cmd' is not installed."
28+
exit 1
29+
fi
30+
done
31+
32+
echo ""
33+
echo -e "${BOLD}Multi-ECU Aggregation Demo - Readiness Check${NC}"
34+
echo "=============================================="
35+
echo ""
36+
37+
# --- 1. Poll health endpoint until it responds ---
38+
echo -n "Waiting for gateway at ${GATEWAY_URL} "
39+
elapsed=0
40+
while [ "$elapsed" -lt "$TIMEOUT" ]; do
41+
if curl -sf "${API_BASE}/health" >/dev/null 2>&1; then
42+
break
43+
fi
44+
echo -n "."
45+
sleep 2
46+
elapsed=$((elapsed + 2))
47+
done
48+
echo ""
49+
50+
if [ "$elapsed" -ge "$TIMEOUT" ]; then
51+
echo_fail "Gateway did not respond within ${TIMEOUT}s"
52+
echo " Start the demo first: ./run-demo.sh"
53+
exit 1
54+
fi
55+
56+
HEALTH_JSON=$(curl -sf "${API_BASE}/health")
57+
status=$(echo "$HEALTH_JSON" | jq -r '.status')
58+
uptime=$(echo "$HEALTH_JSON" | jq -r '.uptime_seconds // "n/a"')
59+
echo_success "Gateway is healthy (status=${status}, uptime=${uptime}s)"
60+
echo ""
61+
62+
# --- 2. Check aggregation peers ---
63+
echo -e "${BOLD}Peer Connections${NC}"
64+
echo "----------------"
65+
66+
PEER_COUNT=$(echo "$HEALTH_JSON" | jq '[.peers // [] | .[] ] | length')
67+
68+
if [ "$PEER_COUNT" -ge 2 ]; then
69+
echo_success "${PEER_COUNT} peers connected"
70+
else
71+
echo_warn "Expected 2 peers, found ${PEER_COUNT}"
72+
fi
73+
74+
# Print individual peer status
75+
echo "$HEALTH_JSON" | jq -r '
76+
.peers // [] | .[] |
77+
" \(.name // .url) - \(.status // "unknown")"
78+
' 2>/dev/null || true
79+
80+
echo ""
81+
82+
# --- 3. Entity counts ---
83+
echo -e "${BOLD}Entity Counts${NC}"
84+
echo "-------------"
85+
86+
for entity in areas components apps functions; do
87+
response=$(curl -sf "${API_BASE}/${entity}" 2>/dev/null) || response=""
88+
if [ -n "$response" ]; then
89+
count=$(echo "$response" | jq '.items | length')
90+
echo_info "${entity}: ${count}"
91+
else
92+
echo_warn "${entity}: endpoint unavailable"
93+
fi
94+
done
95+
96+
echo ""
97+
98+
# --- 4. Summary ---
99+
echo -e "${BOLD}Summary${NC}"
100+
echo "-------"
101+
102+
if [ "$PEER_COUNT" -ge 2 ]; then
103+
echo_success "Demo is ready - all peers connected"
104+
echo ""
105+
echo " Web UI: http://localhost:3000"
106+
echo " REST API: ${API_BASE}/"
107+
echo ""
108+
echo " Try:"
109+
echo " curl ${API_BASE}/components | jq '.items[].id'"
110+
echo " curl ${API_BASE}/functions | jq '.items[].id'"
111+
echo " ./inject-cascade-failure.sh"
112+
exit 0
113+
else
114+
echo_fail "Demo is not fully ready - waiting for peers"
115+
echo ""
116+
echo " Peers may still be booting. Retry in a few seconds:"
117+
echo " ./check-demo.sh"
118+
echo ""
119+
echo " To inspect peer config:"
120+
echo " curl ${API_BASE}/health | jq '.peers'"
121+
exit 1
122+
fi

demos/multi_ecu_aggregation/container_scripts/actuation-ecu/restore-normal/script.bash

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@ if [ $ERRORS -gt 0 ]; then
2626
echo "{\"status\": \"partial\", \"errors\": $ERRORS}"
2727
exit 1
2828
fi
29+
30+
# Clear faults
31+
GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
32+
API_BASE="${GATEWAY_URL}/api/v1"
33+
echo "Clearing faults..."
34+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
35+
sleep 2
36+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
37+
2938
echo '{"status": "restored", "ecu": "actuation"}'

demos/multi_ecu_aggregation/container_scripts/perception-ecu/restore-normal/script.bash

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,13 @@ if [ $ERRORS -gt 0 ]; then
3434
echo "{\"status\": \"partial\", \"errors\": $ERRORS}"
3535
exit 1
3636
fi
37+
38+
# Clear faults
39+
GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
40+
API_BASE="${GATEWAY_URL}/api/v1"
41+
echo "Clearing faults..."
42+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
43+
sleep 2
44+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
45+
3746
echo '{"status": "restored", "ecu": "perception"}'

demos/multi_ecu_aggregation/container_scripts/planning-ecu/restore-normal/script.bash

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,13 @@ if [ $ERRORS -gt 0 ]; then
2525
echo "{\"status\": \"partial\", \"errors\": $ERRORS}"
2626
exit 1
2727
fi
28+
29+
# Clear faults
30+
GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
31+
API_BASE="${GATEWAY_URL}/api/v1"
32+
echo "Clearing faults..."
33+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
34+
sleep 2
35+
curl -sf -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 || true
36+
2837
echo '{"status": "restored", "ecu": "planning"}'

demos/multi_ecu_aggregation/docker-compose.yml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ services:
44
build:
55
context: .
66
dockerfile: Dockerfile
7+
image: multi-ecu-demo:local
78
container_name: perception_ecu
89
hostname: perception-ecu
910
ports:
@@ -20,6 +21,7 @@ services:
2021
build:
2122
context: .
2223
dockerfile: Dockerfile
24+
image: multi-ecu-demo:local
2325
container_name: planning_ecu
2426
hostname: planning-ecu
2527
environment:
@@ -34,6 +36,7 @@ services:
3436
build:
3537
context: .
3638
dockerfile: Dockerfile
39+
image: multi-ecu-demo:local
3740
container_name: actuation_ecu
3841
hostname: actuation-ecu
3942
environment:
@@ -46,13 +49,59 @@ services:
4649
# Web UI for visualization (connects to perception ECU gateway)
4750
medkit-web-ui:
4851
image: ghcr.io/selfpatch/ros2_medkit_web_ui:latest
49-
container_name: ros2_medkit_web_ui
52+
container_name: multi_ecu_web_ui
5053
ports:
5154
- "3000:80"
5255
depends_on:
5356
- perception-ecu
5457
networks: [medkit-net]
5558

59+
# --- CI services (headless, no web UI) ---
60+
61+
# Perception ECU for CI testing
62+
perception-ecu-ci:
63+
profiles: ["ci"]
64+
build:
65+
context: .
66+
dockerfile: Dockerfile
67+
image: multi-ecu-demo:local
68+
container_name: perception_ecu_ci
69+
hostname: perception-ecu
70+
ports:
71+
- "8080:8080"
72+
environment:
73+
- ROS_DOMAIN_ID=10
74+
- ECU_LAUNCH=perception.launch.py
75+
networks: [medkit-net]
76+
77+
# Planning ECU for CI testing
78+
planning-ecu-ci:
79+
profiles: ["ci"]
80+
build:
81+
context: .
82+
dockerfile: Dockerfile
83+
image: multi-ecu-demo:local
84+
container_name: planning_ecu_ci
85+
hostname: planning-ecu
86+
environment:
87+
- ROS_DOMAIN_ID=20
88+
- ECU_LAUNCH=planning.launch.py
89+
networks: [medkit-net]
90+
91+
# Actuation ECU for CI testing
92+
actuation-ecu-ci:
93+
profiles: ["ci"]
94+
build:
95+
context: .
96+
dockerfile: Dockerfile
97+
image: multi-ecu-demo:local
98+
container_name: actuation_ecu_ci
99+
hostname: actuation-ecu
100+
environment:
101+
- ROS_DOMAIN_ID=30
102+
- ECU_LAUNCH=actuation.launch.py
103+
networks: [medkit-net]
104+
56105
networks:
57106
medkit-net:
58107
driver: bridge

0 commit comments

Comments
 (0)