Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
58c22fc
feat(demo): scaffold multi-ECU aggregation demo package
bburda Mar 30, 2026
ada0602
feat(demo): add all 10 ROS 2 nodes for multi-ECU aggregation demo
bburda Mar 30, 2026
902d128
feat(demo): add config files for multi-ECU aggregation demo
bburda Mar 30, 2026
99def4b
feat(demo): add launch files for perception, planning, actuation ECUs
bburda Mar 30, 2026
a8a223e
feat(demo): add Dockerfile and docker-compose for multi-ECU demo
bburda Mar 30, 2026
5c31a75
feat(demo): add shell scripts and container scripts for fault injection
bburda Mar 30, 2026
9fb1a97
docs(demo): add README for multi-ECU aggregation demo
bburda Mar 30, 2026
b49fb6e
fix(demo): correct YAML namespace for gateway params
bburda Mar 30, 2026
36ac3c5
fix(demo): enable aggregation on actuation ECU and hide unmanifested …
bburda Mar 30, 2026
263e522
fix(demo): add infrastructure apps to manifests, use unmanifested_nod…
bburda Mar 31, 2026
c4cc503
fix(demo): change ROS2_MEDKIT_REF from feature branch to main
bburda Apr 2, 2026
f64870c
fix(demo): address review findings - shellcheck, clock type, timer re…
bburda Apr 2, 2026
d49cdda
fix(demo): update member variable on rate fallback in motor_controlle…
bburda Apr 2, 2026
18f4256
fix(demo): update gripper_rate_ on fallback, add explicit geometry_ms…
bburda Apr 2, 2026
f62533b
fix(demo): improve code quality, add CI and smoke test for multi-ECU …
bburda Apr 2, 2026
902abb5
fix(demo): fix actuation namespace references in main() functions
bburda Apr 2, 2026
6782a50
fix(demo): fix domain_bridge node names and relax component assertion…
bburda Apr 2, 2026
9c66caf
fix(demo): check ECU subcomponents at correct endpoint
bburda Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,50 @@ jobs:
if: always()
working-directory: demos/moveit_pick_place
run: docker compose --profile ci down

build-and-test-multi-ecu:
needs: lint
runs-on: ubuntu-24.04
steps:
- name: Show triggering source
if: github.event_name == 'repository_dispatch'
run: |
SHA="${{ github.event.client_payload.sha }}"
RUN_URL="${{ github.event.client_payload.run_url }}"
echo "## Triggered by ros2_medkit" >> "$GITHUB_STEP_SUMMARY"
echo "- Commit: \`${SHA:-unknown}\`" >> "$GITHUB_STEP_SUMMARY"
if [ -n "$RUN_URL" ]; then
echo "- Run: [View triggering run]($RUN_URL)" >> "$GITHUB_STEP_SUMMARY"
else
echo "- Run: (URL not provided)" >> "$GITHUB_STEP_SUMMARY"
fi

- name: Checkout repository
uses: actions/checkout@v4

- name: Build and start multi-ECU demo
working-directory: demos/multi_ecu_aggregation
run: docker compose --profile ci up -d --build perception-ecu-ci planning-ecu-ci actuation-ecu-ci

- name: Run smoke tests
run: ./tests/smoke_test_multi_ecu.sh

- name: Show perception ECU logs on failure
if: failure()
working-directory: demos/multi_ecu_aggregation
run: docker compose --profile ci logs perception-ecu-ci --tail=200

- name: Show planning ECU logs on failure
if: failure()
working-directory: demos/multi_ecu_aggregation
run: docker compose --profile ci logs planning-ecu-ci --tail=200

- name: Show actuation ECU logs on failure
if: failure()
working-directory: demos/multi_ecu_aggregation
run: docker compose --profile ci logs actuation-ecu-ci --tail=200

- name: Teardown
if: always()
working-directory: demos/multi_ecu_aggregation
run: docker compose --profile ci down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ docker-compose.local.yml
.env
.venv/
venv/
demos/multi_ecu_aggregation/launch/__pycache__/
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ This repository contains example integrations and demos that show how ros2_medki
can be used to add SOVD-compliant diagnostics and fault management to ROS 2-based robots and systems.

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

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

**Key Capabilities Demonstrated:**

Expand All @@ -29,8 +30,11 @@ to complete mobile robot integration:
- ✅ Fault management and injection
- ✅ Manifest-based entity discovery
- ✅ Legacy diagnostics bridge support
- ✅ Multi-ECU peer aggregation
- ✅ mDNS-based ECU discovery
- ✅ Cross-ECU function grouping

Both demos support:
All demos support:

- REST API access via SOVD protocol
- Web UI for visualization ([ros2_medkit_web_ui](https://github.com/selfpatch/ros2_medkit_web_ui))
Expand All @@ -44,6 +48,7 @@ Both demos support:
| [Sensor Diagnostics](demos/sensor_diagnostics/) | Lightweight sensor diagnostics demo (no Gazebo required) | Data monitoring, fault injection, dual fault reporting paths | ✅ Ready |
| [TurtleBot3 Integration](demos/turtlebot3_integration/) | Full ros2_medkit integration with TurtleBot3 and Nav2 | SOVD-compliant API, manifest-based discovery, fault management | ✅ Ready |
| [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 |
| [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 |

### Quick Start

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

#### Multi-ECU Aggregation Demo (Advanced - Peer Aggregation)

Multi-ECU demo with 3 independent ECUs aggregated via mDNS discovery:

```bash
cd demos/multi_ecu_aggregation
./run-demo.sh
./check-demo.sh # Verify all 3 ECUs are connected
# Gateway at http://localhost:8080, Web UI at http://localhost:3000

# To stop
./stop-demo.sh
```

**Features:**

- 3 independent ECUs (perception, planning, actuation) each running ros2_medkit
- Peer aggregation via mDNS-based automatic ECU discovery
- Cross-ECU function grouping across the full system
- Unified SOVD-compliant REST API spanning all ECUs
- Web UI for browsing aggregated entity hierarchy

## Getting Started

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

```bash
cd demos/sensor_diagnostics # or turtlebot3_integration
cd demos/sensor_diagnostics # or turtlebot3_integration, moveit_pick_place, multi_ecu_aggregation
# Follow the README.md in that directory
```

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

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).
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).

## Related Projects

Expand Down
68 changes: 68 additions & 0 deletions demos/multi_ecu_aggregation/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.8)
project(multi_ecu_demo)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(vision_msgs REQUIRED)
find_package(diagnostic_msgs REQUIRED)
find_package(rcl_interfaces REQUIRED)
find_package(ros2_medkit_msgs REQUIRED)

# --- Perception ECU nodes ---
add_executable(lidar_driver src/perception/lidar_driver.cpp)
ament_target_dependencies(lidar_driver rclcpp sensor_msgs diagnostic_msgs rcl_interfaces)

add_executable(camera_driver src/perception/camera_driver.cpp)
ament_target_dependencies(camera_driver rclcpp sensor_msgs diagnostic_msgs rcl_interfaces)

add_executable(point_cloud_filter src/perception/point_cloud_filter.cpp)
ament_target_dependencies(point_cloud_filter rclcpp sensor_msgs diagnostic_msgs rcl_interfaces)

add_executable(object_detector src/perception/object_detector.cpp)
ament_target_dependencies(object_detector rclcpp sensor_msgs vision_msgs diagnostic_msgs rcl_interfaces)

# --- Planning ECU nodes ---
add_executable(path_planner src/planning/path_planner.cpp)
ament_target_dependencies(path_planner rclcpp nav_msgs geometry_msgs vision_msgs diagnostic_msgs rcl_interfaces)

add_executable(behavior_planner src/planning/behavior_planner.cpp)
ament_target_dependencies(behavior_planner rclcpp nav_msgs geometry_msgs diagnostic_msgs rcl_interfaces)

add_executable(task_scheduler src/planning/task_scheduler.cpp)
ament_target_dependencies(task_scheduler rclcpp std_msgs diagnostic_msgs rcl_interfaces)

# --- Actuation ECU nodes ---
add_executable(motor_controller src/actuation/motor_controller.cpp)
ament_target_dependencies(motor_controller rclcpp sensor_msgs geometry_msgs diagnostic_msgs rcl_interfaces)

add_executable(joint_driver src/actuation/joint_driver.cpp)
ament_target_dependencies(joint_driver rclcpp sensor_msgs diagnostic_msgs rcl_interfaces)

add_executable(gripper_controller src/actuation/gripper_controller.cpp)
ament_target_dependencies(gripper_controller rclcpp sensor_msgs geometry_msgs diagnostic_msgs rcl_interfaces)

# --- Install ---
install(TARGETS
lidar_driver camera_driver point_cloud_filter object_detector
path_planner behavior_planner task_scheduler
motor_controller joint_driver gripper_controller
DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
install(DIRECTORY config/ DESTINATION share/${PROJECT_NAME}/config)

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()

ament_package()
71 changes: 71 additions & 0 deletions demos/multi_ecu_aggregation/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Multi-ECU Aggregation Demo
FROM ros:jazzy-ros-base

ENV DEBIAN_FRONTEND=noninteractive
ENV ROS_DISTRO=jazzy
ENV COLCON_WS=/root/demo_ws

# Install minimal dependencies (no GUI/simulation)
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-vision-msgs \
ros-${ROS_DISTRO}-nav-msgs \
ros-${ROS_DISTRO}-domain-bridge \
ros-${ROS_DISTRO}-ament-lint-auto \
ros-${ROS_DISTRO}-ament-lint-common \
ros-${ROS_DISTRO}-ament-cmake-gtest \
ros-${ROS_DISTRO}-yaml-cpp-vendor \
ros-${ROS_DISTRO}-example-interfaces \
python3-colcon-common-extensions \
python3-requests \
nlohmann-json3-dev \
libcpp-httplib-dev \
libsystemd-dev \
sqlite3 \
libsqlite3-dev \
git \
curl \
jq \
&& rm -rf /var/lib/apt/lists/*

# Clone ros2_medkit from GitHub (gateway + dependencies + plugins)
ARG ROS2_MEDKIT_REF=main
WORKDIR ${COLCON_WS}/src
RUN git clone --depth 1 --branch ${ROS2_MEDKIT_REF} https://github.com/selfpatch/ros2_medkit.git && \
mv ros2_medkit/src/ros2_medkit_cmake . && \
mv ros2_medkit/src/ros2_medkit_gateway . && \
mv ros2_medkit/src/ros2_medkit_serialization . && \
mv ros2_medkit/src/ros2_medkit_msgs . && \
mv ros2_medkit/src/ros2_medkit_fault_manager . && \
mv ros2_medkit/src/ros2_medkit_fault_reporter . && \
mv ros2_medkit/src/ros2_medkit_diagnostic_bridge . && \
mv ros2_medkit/src/ros2_medkit_plugins/ros2_medkit_graph_provider . && \
rm -rf ros2_medkit

# Copy demo package
COPY package.xml CMakeLists.txt ${COLCON_WS}/src/multi_ecu_demo/
COPY src/ ${COLCON_WS}/src/multi_ecu_demo/src/
COPY config/ ${COLCON_WS}/src/multi_ecu_demo/config/
COPY launch/ ${COLCON_WS}/src/multi_ecu_demo/launch/

# Copy container scripts
COPY container_scripts/ /var/lib/ros2_medkit/scripts/
RUN find /var/lib/ros2_medkit/scripts -name "*.bash" -exec chmod +x {} \;

# Build all packages (skip test dependencies that aren't in ros-base)
WORKDIR ${COLCON_WS}
RUN bash -c "source /opt/ros/jazzy/setup.bash && \
rosdep update && \
rosdep install --from-paths src --ignore-src -r -y \
--skip-keys='ament_cmake_clang_format ament_cmake_clang_tidy test_msgs example_interfaces sqlite3' && \
colcon build --symlink-install --cmake-args -DBUILD_TESTING=OFF"

# Setup environment
RUN echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc && \
echo "source ${COLCON_WS}/install/setup.bash" >> ~/.bashrc

# Expose gateway port
EXPOSE 8080

# ECU_LAUNCH env var selects which launch file to run
ENV ECU_LAUNCH=perception.launch.py
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}\""]
Loading
Loading