diff --git a/README.md b/README.md
index 33c5e0e..b562408 100644
--- a/README.md
+++ b/README.md
@@ -8,19 +8,41 @@ with real ROS 2 systems.
## Overview
This repository contains example integrations and demos that show how ros2_medkit
-can be used to add modern diagnostics to ROS 2-based robots and systems.
+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, starting from basic integration and
-progressing toward more advanced use cases.
+Each demo builds on real-world scenarios, progressing from simple sensor monitoring
+to complete mobile robot integration:
+
+- **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
+
+**Key Capabilities Demonstrated:**
+
+- ✅ SOVD-compliant REST API (Areas → Components → Apps → Functions)
+- ✅ Real-time data access (topics via HTTP)
+- ✅ Configuration management (ROS 2 parameters via HTTP)
+- ✅ Operation execution (services and actions via HTTP)
+- ✅ Fault management and injection
+- ✅ Manifest-based entity discovery
+- ✅ Legacy diagnostics bridge support
+
+Both demos support:
+
+- REST API access via SOVD protocol
+- Web UI for visualization ([sovd_web_ui](https://github.com/selfpatch/sovd_web_ui))
+- Fault injection and monitoring
+- Docker deployment for easy setup
## Demos
-| Demo | Description | Status |
-|------|-------------|--------|
-| [Sensor Diagnostics](demos/sensor_diagnostics/) | Lightweight sensor diagnostics demo (no Gazebo required) | ✅ Ready |
-| [TurtleBot3 Integration](demos/turtlebot3_integration/) | Full ros2_medkit integration with TurtleBot3 and Nav2 | 🚧 In Progress |
+| Demo | Description | Features | Status |
+|------|-------------|----------|--------|
+| [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 |
+
+### Quick Start
-### Quick Start (Sensor Diagnostics)
+#### Sensor Diagnostics Demo (Fastest - No GPU Required)
The sensor diagnostics demo is the fastest way to try ros2_medkit:
@@ -31,12 +53,40 @@ docker compose up
# Run ./run-demo.sh for an interactive walkthrough
```
+**Features:**
+
+- Simulated sensors (LiDAR, IMU, GPS, Camera)
+- Configurable fault injection via REST API
+- Dual fault reporting paths (legacy + modern)
+- Runs anywhere (CI, Codespaces, laptop)
+
+#### TurtleBot3 + Nav2 Demo (Full Navigation Stack)
+
+Full mobile robot demo with autonomous navigation:
+
+```bash
+cd demos/turtlebot3_integration
+./run-demo.sh
+# Gazebo will open, Web UI at http://localhost:3000
+# Try: ./send-nav-goal.sh 2.0 0.5
+```
+
+**Features:**
+
+- Complete TurtleBot3 simulation in Gazebo
+- Nav2 navigation stack integration
+- SOVD-compliant REST API with entity hierarchy
+- Manifest-based discovery (Areas → Components → Apps → Functions)
+- Fault injection scenarios for Nav2 components
+- Real-time robot control via HTTP
+
## Getting Started
### Prerequisites
- ROS 2 Jazzy (Ubuntu 24.04)
-- [ros2_medkit](https://github.com/selfpatch/ros2_medkit) installed
+- Docker and docker-compose (recommended)
+- [ros2_medkit](https://github.com/selfpatch/ros2_medkit) >= 1.0.0
### Clone the Repository
@@ -47,16 +97,44 @@ cd selfpatch_demos
### Run a Demo
-Each demo has its own README with specific instructions. Start with:
+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/turtlebot3_integration
+cd demos/sensor_diagnostics # or turtlebot3_integration
# Follow the README.md in that directory
```
+## Example API Usage
+
+All demos expose a SOVD-compliant REST API. Here are some common operations:
+
+```bash
+# Check gateway health
+curl http://localhost:8080/api/v1/health
+
+# List all apps (ROS 2 nodes)
+curl http://localhost:8080/api/v1/apps | jq '.items[] | {id, name}'
+
+# Get sensor data
+curl http://localhost:8080/api/v1/apps/lidar_sim/data/scan | jq
+
+# Update configuration
+curl -X PUT http://localhost:8080/api/v1/apps/lidar_sim/configurations/noise_stddev \
+ -H "Content-Type: application/json" \
+ -d '{"value": 0.5}'
+
+# List active faults
+curl http://localhost:8080/api/v1/faults | jq
+```
+
+See individual demo READMEs for more examples.
+
## Related Projects
-- [ros2_medkit](https://github.com/selfpatch/ros2_medkit) — The core diagnostics library
+- [ros2_medkit](https://github.com/selfpatch/ros2_medkit) — Core diagnostics library with SOVD-compliant gateway
+- [sovd_web_ui](https://github.com/selfpatch/sovd_web_ui) — Web-based visualization and control interface
+- [ros2_medkit_mcp](https://github.com/selfpatch/ros2_medkit_mcp) — MCP server for LLM integration
- [ros2_medkit documentation](https://selfpatch.github.io/ros2_medkit/) — Full documentation and API reference
## Contributing
@@ -71,4 +149,4 @@ If you discover a security vulnerability, please follow the process in [`SECURIT
## License
-This project is licensed under the Apache License 2.0. See the [`LICENSE`](LICENSE) file for details.
\ No newline at end of file
+This project is licensed under the Apache License 2.0. See the [`LICENSE`](LICENSE) file for details.
diff --git a/demos/sensor_diagnostics/package.xml b/demos/sensor_diagnostics/package.xml
index e6d8e11..6ab302a 100644
--- a/demos/sensor_diagnostics/package.xml
+++ b/demos/sensor_diagnostics/package.xml
@@ -4,7 +4,7 @@
sensor_diagnostics_demo
0.1.0
Lightweight sensor diagnostics demo for ros2_medkit (no Gazebo required)
- Demo Maintainer
+ bburda
Apache-2.0
ament_cmake
diff --git a/demos/turtlebot3_integration/Dockerfile b/demos/turtlebot3_integration/Dockerfile
index ae13768..8464769 100644
--- a/demos/turtlebot3_integration/Dockerfile
+++ b/demos/turtlebot3_integration/Dockerfile
@@ -37,6 +37,8 @@ RUN apt-get update && apt-get install -y \
python3-requests \
nlohmann-json3-dev \
libcpp-httplib-dev \
+ sqlite3 \
+ libsqlite3-dev \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
@@ -47,6 +49,9 @@ RUN git clone --depth 1 --recurse-submodules https://github.com/selfpatch/ros2_m
mv ros2_medkit/src/ros2_medkit_gateway \
ros2_medkit/src/ros2_medkit_msgs \
ros2_medkit/src/ros2_medkit_serialization \
+ ros2_medkit/src/ros2_medkit_fault_manager \
+ ros2_medkit/src/ros2_medkit_fault_reporter \
+ ros2_medkit/src/ros2_medkit_diagnostic_bridge \
ros2_medkit/src/dynamic_message_introspection . && \
rm -rf ros2_medkit
diff --git a/demos/turtlebot3_integration/README.md b/demos/turtlebot3_integration/README.md
index 671c0e0..eac671b 100644
--- a/demos/turtlebot3_integration/README.md
+++ b/demos/turtlebot3_integration/README.md
@@ -1,11 +1,11 @@
# TurtleBot3 Integration Demo with Nav2 Navigation
This demo shows how to integrate ros2_medkit with TurtleBot3 and Nav2 navigation stack
-to provide modern diagnostics and control for a mobile robot system via REST API.
+to provide SOVD-compliant diagnostics, fault management, and control for a mobile robot system via REST API.
## Status
-✅ **Demo Ready** - Full navigation demo with Web UI
+✅ **Demo Ready** - Full navigation demo with Web UI and fault management
## Overview
@@ -13,10 +13,11 @@ This demo demonstrates:
- Launching TurtleBot3 simulation in Gazebo with turtlebot3_world
- Running Nav2 navigation stack (AMCL, planner, controller)
-- Running ros2_medkit gateway alongside the robot
-- Discovering TurtleBot3 nodes through REST API
-- Querying and publishing to ROS2 topics via HTTP
-- **NEW:** Controlling the robot via sovd_web_ui
+- Running ros2_medkit gateway with **manifest-based discovery**
+- Fault management via **diagnostic_bridge** (legacy /diagnostics support)
+- Querying robot data via **REST API**
+- Entity hierarchy: Areas → Components → Apps → Functions
+- Controlling the robot via sovd_web_ui
## Prerequisites
@@ -40,14 +41,34 @@ That's it! The script will:
3. Launch TurtleBot3 simulation + Nav2 + ros2_medkit gateway
4. Launch sovd_web_ui at
+**Note:** By default, the demo runs with **Gazebo GUI** for visualization. Requires X11 display.
+
+### Running Headless (Server Only)
+
+For CI/CD or remote servers without display:
+
+```bash
+HEADLESS=true docker compose up
+# or:
+./run-demo.sh --headless
+```
+
+### Running with GUI (Default)
+
+With Gazebo visualization:
+
+```bash
+docker compose up
+# or:
+./run-demo.sh
+```
+
### 2. Access the Web UI
The Web UI is automatically started by docker-compose and available at .
Connect to the gateway using `http://localhost:8080/api/v1` in the connection dialog.
-**Note:** The first build will take longer as it clones and builds sovd_web_ui from GitHub.
-
### With NVIDIA GPU
For hardware-accelerated Gazebo rendering with NVIDIA GPU:
@@ -72,8 +93,8 @@ docker compose --profile nvidia up --build
### Via Web UI
1. Connect to the gateway in sovd_web_ui
-2. In the "ROS2 Topics" panel on the right, select `/cmd_vel`
-3. Enter velocity command JSON:
+2. Find entity with `/cmd_vel` data
+3. Enter velocity command JSON (or use form with fields from schema):
```json
{"linear": {"x": 0.2}, "angular": {"z": 0.0}}
@@ -84,23 +105,15 @@ docker compose --profile nvidia up --build
### Via Command Line
```bash
-# Send velocity command (moves robot forward)
-curl -X POST http://localhost:8080/api/v1/topics/publish \
+# Send velocity command using Apps data endpoint (moves robot forward)
+curl -X PUT http://localhost:8080/api/v1/apps/turtlebot3-node/data/cmd_vel \
-H "Content-Type: application/json" \
- -d '{
- "topic": "/cmd_vel",
- "type": "geometry_msgs/msg/Twist",
- "data": {"linear": {"x": 0.2, "y": 0.0, "z": 0.0}, "angular": {"x": 0.0, "y": 0.0, "z": 0.0}}
- }'
+ -d '{"linear": {"x": 0.2, "y": 0.0, "z": 0.0}, "angular": {"x": 0.0, "y": 0.0, "z": 0.0}}'
# Stop the robot
-curl -X POST http://localhost:8080/api/v1/topics/publish \
+curl -X PUT http://localhost:8080/api/v1/apps/turtlebot3-node/data/cmd_vel \
-H "Content-Type: application/json" \
- -d '{
- "topic": "/cmd_vel",
- "type": "geometry_msgs/msg/Twist",
- "data": {"linear": {"x": 0.0}, "angular": {"z": 0.0}}
- }'
+ -d '{"linear": {"x": 0.0}, "angular": {"z": 0.0}}'
```
### Via ROS2 CLI (inside container)
@@ -117,74 +130,227 @@ ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
## REST API Endpoints
-### Discovery
+### Discovery (SOVD Entity Hierarchy)
```bash
# Check gateway health
curl http://localhost:8080/api/v1/health
-# List discovered areas
-curl http://localhost:8080/api/v1/areas
+# List discovered areas (namespace groupings)
+curl http://localhost:8080/api/v1/areas | jq '.items[] | {id, name}'
+
+# List all components (hardware/logical units)
+curl http://localhost:8080/api/v1/components | jq '.items[] | {id, name, area}'
-# List all discovered components (nodes)
-curl http://localhost:8080/api/v1/components
+# List all apps (ROS 2 nodes)
+curl http://localhost:8080/api/v1/apps | jq '.items[] | {id, name, namespace}'
+
+# Get specific app details
+curl http://localhost:8080/api/v1/apps/amcl | jq
```
-### Topics
+### Data Access (via Apps)
```bash
-# List all topics
-curl http://localhost:8080/api/v1/topics
+# Get LiDAR scan data
+curl http://localhost:8080/api/v1/apps/turtlebot3-node/data/scan | jq '{
+ angle_min: .angle_min,
+ angle_max: .angle_max,
+ sample_ranges: .ranges[:5]
+}'
+
+# Get odometry data
+curl http://localhost:8080/api/v1/apps/turtlebot3-node/data/odom | jq '{
+ position: .pose.pose.position,
+ orientation: .pose.pose.orientation
+}'
+
+# List all data topics for an app
+curl http://localhost:8080/api/v1/apps/turtlebot3-node/data | jq
+```
-# Get topic details (URL-encode topic name: / -> %2F)
-curl http://localhost:8080/api/v1/topics/%2Fcmd_vel
+### Fault Management
+
+```bash
+# List all active faults
+curl http://localhost:8080/api/v1/faults | jq
-# Get topic without sample
-curl "http://localhost:8080/api/v1/topics/%2Fcmd_vel?sample=false"
+# Get faults for a specific area
+curl http://localhost:8080/api/v1/areas/robot/faults | jq
-# Publish to topic (see examples above)
-curl -X POST http://localhost:8080/api/v1/topics/publish ...
+# Clear a specific fault
+curl -X DELETE http://localhost:8080/api/v1/apps/diagnostic-bridge/faults/TURTLEBOT3_NODE
+```
+
+### Operations (Service Calls)
+
+```bash
+# List available operations for an app
+curl http://localhost:8080/api/v1/apps/amcl/operations | jq
+
+# Execute an operation (service call)
+curl -X POST http://localhost:8080/api/v1/apps/amcl/operations/reinitialize_global_localization/executions \
+ -H "Content-Type: application/json" \
+ -d '{}'
+```
+
+### Configurations (Parameters)
+
+```bash
+# List node parameters
+curl http://localhost:8080/api/v1/apps/amcl/configurations | jq
+
+# Get a specific parameter
+curl http://localhost:8080/api/v1/apps/amcl/configurations/max_particles | jq
+
+# Update a parameter
+curl -X PUT http://localhost:8080/api/v1/apps/amcl/configurations/max_particles \
+ -H "Content-Type: application/json" \
+ -d '{"value": 3000}'
```
## What You'll See
-When TurtleBot3 simulation starts with Nav2, ros2_medkit will discover nodes organized into **areas** based on their ROS 2 namespaces:
+When TurtleBot3 simulation starts with Nav2, ros2_medkit will discover nodes organized into the **SOVD entity hierarchy** defined by the manifest:
+
+### Entity Hierarchy
+
+```
+TurtleBot3 Demo (manifest-based discovery)
+├── Areas (namespace groupings)
+│ ├── robot → TurtleBot3 hardware
+│ ├── navigation → Nav2 stack
+│ ├── diagnostics → ros2_medkit gateway
+│ └── bridge → Diagnostic bridge
+├── Components (hardware/logical units)
+│ ├── turtlebot3-base → Robot platform (area: robot)
+│ ├── lidar-sensor → LiDAR scanner (area: robot)
+│ ├── nav2-stack → Navigation (area: navigation)
+│ ├── gateway → REST API (area: diagnostics)
+│ ├── fault-manager → Fault aggregation (area: diagnostics)
+│ └── diagnostic-bridge-unit → Legacy support (area: bridge)
+├── Apps (ROS 2 nodes)
+│ ├── turtlebot3-node → component: turtlebot3-base
+│ ├── robot-state-publisher → component: turtlebot3-base
+│ ├── amcl → component: nav2-stack
+│ ├── bt-navigator → component: nav2-stack
+│ ├── controller-server → component: nav2-stack
+│ ├── planner-server → component: nav2-stack
+│ ├── medkit-gateway → component: gateway
+│ ├── medkit-fault-manager → component: fault-manager
+│ └── diagnostic-bridge → component: diagnostic-bridge-unit
+└── Functions (high-level capabilities)
+ ├── autonomous-navigation → hosted by: amcl, bt-navigator, ...
+ ├── robot-control → hosted by: turtlebot3-node, velocity-smoother
+ └── fault-management → hosted by: gateway, fault-manager, bridge
+```
+
+### Fault Reporting
+
+This demo uses the **legacy fault reporting path** via diagnostic_bridge:
+
+```
+Nav2/TurtleBot3 nodes → /diagnostics topic (DiagnosticArray)
+ ↓
+ diagnostic_bridge subscribes and converts
+ ↓
+ FaultManager receives via ReportFault service
+ ↓
+ Gateway exposes via GET /api/v1/faults
+```
+
+| Source | Fault Reporter | Example Faults |
+|--------|----------------|----------------|
+| AMCL | diagnostic_bridge | Localization degraded |
+| Nav2 Controller | diagnostic_bridge | Path following errors |
+| TurtleBot3 | diagnostic_bridge | Motor/sensor issues |
+
+## Fault Injection Scenarios
+
+This demo includes scripts to inject various fault conditions for testing fault management:
+
+### Available Fault Scenarios
-### Areas (Namespaces)
+| Script | Fault Type | Description | Expected Faults |
+|--------|-----------|-------------|-----------------|
+| `inject-nav-failure.sh` | Navigation | Send goal to unreachable location | BT_NAVIGATOR, PLANNER_SERVER |
+| `inject-localization-failure.sh` | Localization | Reset AMCL with high uncertainty | AMCL |
+| `inject-controller-failure.sh` | Controller | Set very restrictive velocity limits | VELOCITY_SMOOTHER, CONTROLLER_SERVER |
+| `inject-collision.sh` | Collision | Navigate toward obstacles | COLLISION_MONITOR |
+| `restore-normal.sh` | Recovery | Restore defaults and clear faults | - |
-| Area | Namespace | Description |
-|------|-----------|-------------|
-| `root` | `/` | TurtleBot3, Nav2, and Gazebo nodes |
-| `diagnostics` | `/diagnostics` | ros2_medkit gateway |
+### Fault Injection Examples
-### Components
+#### 1. Navigation Failure
-**Root** (`/`) - Main robot system:
+```bash
+# Send robot to unreachable goal (outside map bounds)
+./inject-nav-failure.sh
-- `turtlebot3_node` - Main robot interface
-- `robot_state_publisher` - TF tree publisher
-- `gazebo` - Simulation engine
-- `amcl` - Adaptive Monte Carlo Localization
-- `bt_navigator` - Behavior Tree Navigator
-- `controller_server` - Path following controller
-- `planner_server` - Global path planner
-- `velocity_smoother` - Velocity command smoother
-- Various lifecycle and manager nodes
+# Check resulting faults
+curl http://localhost:8080/api/v1/faults | jq '.items[] | {code, severity, message}'
+```
-**Diagnostics** (`/diagnostics`):
+#### 2. Localization Failure
-- `ros2_medkit_gateway` - REST API gateway
+```bash
+# Reinitialize AMCL global localization (causes high uncertainty)
+./inject-localization-failure.sh
+
+# Watch localization recover
+curl http://localhost:8080/api/v1/apps/amcl/data/particlecloud | jq '.poses | length'
+```
-This demonstrates ros2_medkit's ability to discover ROS 2 nodes and organize them into areas.
-For a more hierarchical demo with multiple areas, see the [ros2_medkit demo nodes](https://github.com/selfpatch/ros2_medkit/tree/main/src/ros2_medkit_gateway#demo-nodes) which use namespaces like `/powertrain`, `/chassis`, and `/body`.
+#### 3. Controller Restriction
+
+```bash
+# Set very low velocity limits (robot moves extremely slowly)
+./inject-controller-failure.sh
+
+# Try sending a navigation goal - robot will struggle to follow path
+./send-nav-goal.sh 2.0 0.5
+```
+
+#### 4. Collision Scenario
+
+```bash
+# Trigger collision avoidance
+./inject-collision.sh
+
+# Monitor collision warnings
+curl http://localhost:8080/api/v1/faults | jq
+```
+
+#### Restore Normal Operation
+
+```bash
+# Clear all faults and restore default parameters
+./restore-normal.sh
+```
+
+### Fault Monitoring via API
+
+```bash
+# List all active faults
+curl http://localhost:8080/api/v1/faults | jq
+
+# Get faults for navigation area
+curl http://localhost:8080/api/v1/areas/navigation/faults | jq
+
+# Clear specific fault
+curl -X DELETE http://localhost:8080/api/v1/faults/{fault_id}
+
+# Clear all faults
+curl -X DELETE http://localhost:8080/api/v1/faults
+```
## Architecture
```text
-┌─────────────────────────────────────────────────────────────────────┐
+┌──────────────────────────────────────────────────────────────────────┐
│ Docker Container │
-│ ┌──────────────────────────────────────────────────────────────┐ │
-│ │ Gazebo Simulation │ │
+│ ┌───────────────────────────────────────────────────────────────┐ │
+│ │ Gazebo Simulation │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌────────┐ ┌───────────┐ │ │
│ │ │ TurtleBot3 │ │ robot_state │ │ LIDAR │ │ Nav2 │ │ │
│ │ │ Node │ │ publisher │ │ sensor │ │ (AMCL, │ │ │
@@ -194,18 +360,27 @@ For a more hierarchical demo with multiple areas, see the [ros2_medkit demo node
│ │ ROS 2 Topics ◄────────────────────┘ │ │
│ └──────────────────────────┼────────────────────────────────────┘ │
│ │ │
-│ ┌─────────────┴─────────────┐ │
-│ │ ros2_medkit Gateway │ │
-│ │ (REST Server :8080) │ │
-│ └─────────────┬─────────────┘ │
-└─────────────────────────────┼────────────────────────────────────────┘
- │
- HTTP REST API
- │
- ┌────────────────────┼────────────────────┐
- ▼ ▼ ▼
- sovd_web_ui curl/browser External tools
- (localhost:3000)
+│ ┌─────────────────┼─────────────────┐ │
+│ │ │ │ │
+│ ▼ ▼ ▼ │
+│ ┌────────────────┐ ┌─────────────┐ ┌──────────────────┐ │
+│ │ /diagnostics │ │ fault_ │ │ ros2_medkit │ │
+│ │ topic │ │ manager │ │ Gateway │ │
+│ └───────┬────────┘ └──────▲──────┘ │ (REST :8080) │ │
+│ │ │ └────────┬─────────┘ │
+│ ▼ │ │ │
+│ ┌────────────────┐ │ │ │
+│ │ diagnostic_ ├─────────┘ │ │
+│ │ bridge │ │ │
+│ └────────────────┘ │ │
+└────────────────────────────────────────────────┼─────────────────────┘
+ │
+ HTTP REST API
+ │
+ ┌───────────────────────────────────────┼────────────────────┐
+ ▼ ▼ ▼ ▼
+ sovd_web_ui curl/browser External tools MCP Server
+ (localhost:3000) (ros2_medkit_mcp)
```
## File Structure
@@ -215,14 +390,37 @@ demos/turtlebot3_integration/
├── Dockerfile # ROS 2 Jazzy + TurtleBot3 + Nav2 + ros2_medkit
├── docker-compose.yml # Docker Compose (CPU & GPU via profiles)
├── run-demo.sh # One-click demo launcher
+├── send-nav-goal.sh # Send navigation goal via SOVD API
+├── check-entities.sh # Explore SOVD entity hierarchy
+├── check-faults.sh # View active faults
+├── inject-nav-failure.sh # Inject navigation failure scenario
+├── inject-localization-failure.sh # Inject AMCL localization issues
+├── inject-controller-failure.sh # Inject controller velocity limits
+├── inject-collision.sh # Inject collision warning scenario
+├── restore-normal.sh # Restore normal operation
├── config/
-│ ├── medkit_params.yaml # ros2_medkit gateway config
-│ ├── nav2_params.yaml # Nav2 navigation parameters
-│ └── turtlebot3_world.yaml # Map configuration
+│ ├── medkit_params.yaml # ros2_medkit gateway config
+│ ├── turtlebot3_manifest.yaml # SOVD manifest (entity hierarchy)
+│ ├── nav2_params.yaml # Nav2 navigation parameters
+│ └── turtlebot3_world.yaml # Map configuration
└── launch/
└── demo.launch.py # ROS 2 launch file
```
+## Scripts
+
+| Script | Description |
+|--------|-------------|
+| `run-demo.sh` | Start the full demo (Docker) |
+| `send-nav-goal.sh [x] [y] [yaw]` | Send navigation goal via SOVD API |
+| `check-entities.sh` | Explore SOVD entity hierarchy |
+| `check-faults.sh` | View active faults from gateway |
+| `inject-nav-failure.sh` | Inject navigation failure (unreachable goal) |
+| `inject-localization-failure.sh` | Inject localization failure (AMCL reset) |
+| `inject-controller-failure.sh` | Inject controller failure (velocity limits) |
+| `inject-collision.sh` | Inject collision warning scenario |
+| `restore-normal.sh` | Restore normal operation and clear faults |
+
## Manual Setup (Alternative)
If you prefer not to use Docker:
diff --git a/demos/turtlebot3_integration/check-entities.sh b/demos/turtlebot3_integration/check-entities.sh
new file mode 100755
index 0000000..5b04968
--- /dev/null
+++ b/demos/turtlebot3_integration/check-entities.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+# Explore SOVD entity hierarchy from ros2_medkit gateway
+# Demonstrates: Areas → Components → Apps → Functions
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+# Colors for output
+BLUE='\033[0;34m'
+GREEN='\033[0;32m'
+NC='\033[0m'
+
+echo_step() {
+ echo -e "\n${BLUE}=== $1 ===${NC}\n"
+}
+
+echo "╔════════════════════════════════════════════════════════════╗"
+echo "║ SOVD Entity Hierarchy Explorer ║"
+echo "║ TurtleBot3 + Nav2 Demo ║"
+echo "╚════════════════════════════════════════════════════════════╝"
+
+# Check for jq dependency
+if ! command -v jq >/dev/null 2>&1; then
+ echo "❌ 'jq' is required but not installed."
+ echo " Please install jq (e.g., 'sudo apt-get install jq') and retry."
+ exit 1
+fi
+
+# Wait for gateway
+echo ""
+echo "Checking gateway health..."
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ echo " Start with: ./run-demo.sh"
+ exit 1
+fi
+echo "✓ Gateway is healthy"
+
+echo_step "1. Areas (Namespace Groupings)"
+curl -s "${API_BASE}/areas" | jq '.items[] | {id: .id, name: .name, description: .description}'
+
+echo_step "2. Components (Hardware/Logical Units)"
+curl -s "${API_BASE}/components" | jq '.items[] | {id: .id, name: .name, type: .type, area: .area}'
+
+echo_step "3. Apps (ROS 2 Nodes)"
+curl -s "${API_BASE}/apps" | jq '.items[] | {id: .id, name: .name, category: .category, component: .is_located_on}'
+
+echo_step "4. Functions (High-level Capabilities)"
+curl -s "${API_BASE}/functions" | jq '.items[] | {id: .id, name: .name, category: .category, hosted_by: .hosted_by}'
+
+echo_step "5. Sample Data (LiDAR Scan)"
+echo "Getting latest LiDAR scan from TurtleBot3..."
+curl -s "${API_BASE}/apps/turtlebot3-node/data/scan" 2>/dev/null | jq '{
+ angle_min: .angle_min,
+ angle_max: .angle_max,
+ range_min: .range_min,
+ range_max: .range_max,
+ sample_ranges: .ranges[:5]
+}' || echo " (LiDAR data not available - Gazebo may still be starting)"
+
+echo_step "6. Faults"
+curl -s "${API_BASE}/faults" | jq '.items[] | {code: .code, severity: .severity, reporter: .reporter_id}'
+
+echo ""
+echo -e "${GREEN}✓ Entity hierarchy exploration complete!${NC}"
+echo ""
+echo "Try more commands:"
+echo " curl ${API_BASE}/apps/amcl/configurations | jq # AMCL parameters"
+echo " curl ${API_BASE}/apps/amcl/operations | jq # Available services"
+echo " curl ${API_BASE}/components/nav2-stack/hosts | jq # Apps on Nav2 stack"
diff --git a/demos/turtlebot3_integration/check-faults.sh b/demos/turtlebot3_integration/check-faults.sh
new file mode 100755
index 0000000..944ed6d
--- /dev/null
+++ b/demos/turtlebot3_integration/check-faults.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Check current faults from ros2_medkit gateway
+# Faults are collected from Nav2/TurtleBot3 via diagnostic_bridge
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "🔍 Checking faults from ros2_medkit gateway..."
+echo ""
+
+# Check for jq dependency
+if ! command -v jq >/dev/null 2>&1; then
+ echo "❌ 'jq' is required but not installed."
+ echo " Please install jq (e.g., 'sudo apt-get install jq') and retry."
+ exit 1
+fi
+
+# Wait for gateway
+echo "Checking gateway health..."
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ echo " Start with: ./run-demo.sh"
+ exit 1
+fi
+echo "✓ Gateway is healthy"
+echo ""
+
+# Get all faults
+echo "📋 Active Faults:"
+FAULTS=$(curl -s "${API_BASE}/faults")
+
+# Check if there are any faults
+FAULT_COUNT=$(echo "$FAULTS" | jq '.items | length')
+
+if [ "$FAULT_COUNT" = "0" ]; then
+ echo " No active faults - system is healthy!"
+else
+ echo "$FAULTS" | jq '.items[] | {
+ code: .code,
+ severity: .severity,
+ reporter: .reporter_id,
+ message: .message,
+ timestamp: .timestamp
+ }'
+fi
+
+echo ""
+echo "📊 Fault Summary:"
+echo " Total active faults: $FAULT_COUNT"
+echo ""
+echo "Commands:"
+echo " Clear all faults: curl -X DELETE ${API_BASE}/faults"
+echo " Check specific area: curl ${API_BASE}/areas/robot/faults | jq"
diff --git a/demos/turtlebot3_integration/config/medkit_params.yaml b/demos/turtlebot3_integration/config/medkit_params.yaml
index 0b640a3..74a30b9 100644
--- a/demos/turtlebot3_integration/config/medkit_params.yaml
+++ b/demos/turtlebot3_integration/config/medkit_params.yaml
@@ -8,13 +8,22 @@ diagnostics:
host: "0.0.0.0"
port: 8080
- refresh_interval_ms: 2000
+ refresh_interval_ms: 10000 # 10 seconds (default), reduces log spam
cors:
allowed_origins: ["*"]
- allowed_methods: ["GET", "PUT", "POST", "OPTIONS"]
+ allowed_methods: ["GET", "PUT", "POST", "DELETE", "OPTIONS"]
allowed_headers: ["Content-Type", "Accept"]
allow_credentials: false
max_age_seconds: 86400
max_parallel_topic_samples: 10
+
+ # Discovery configuration
+ discovery_mode: "hybrid" # runtime_only, manifest_only, or hybrid
+ manifest_path: "" # Will be set via launch argument
+ manifest_strict_validation: true
+
+ discovery:
+ runtime:
+ create_synthetic_components: false # Manifest defines components
diff --git a/demos/turtlebot3_integration/config/turtlebot3_manifest.yaml b/demos/turtlebot3_integration/config/turtlebot3_manifest.yaml
new file mode 100644
index 0000000..d931bf1
--- /dev/null
+++ b/demos/turtlebot3_integration/config/turtlebot3_manifest.yaml
@@ -0,0 +1,215 @@
+# SOVD Manifest for TurtleBot3 + Nav2 Integration Demo
+# Defines the entity hierarchy for ros2_medkit gateway
+manifest_version: "1.0"
+
+metadata:
+ name: "turtlebot3-nav2-demo"
+ description: "TurtleBot3 simulation with Nav2 navigation and ros2_medkit diagnostics"
+ version: "0.1.0"
+
+config:
+ unmanifested_nodes: warn
+ inherit_runtime_resources: true
+
+# =============================================================================
+# AREAS - Functional groupings by namespace
+# =============================================================================
+areas:
+ - id: robot
+ name: "Robot"
+ description: "TurtleBot3 robot hardware and drivers"
+ namespace: /
+
+ - id: navigation
+ name: "Navigation"
+ description: "Nav2 navigation stack"
+ namespace: /
+
+ - id: diagnostics
+ name: "Diagnostics"
+ description: "ros2_medkit gateway and fault management"
+ namespace: /diagnostics
+
+ - id: bridge
+ name: "Bridge"
+ description: "Legacy diagnostics bridge"
+ namespace: /bridge
+
+# =============================================================================
+# COMPONENTS - Hardware/logical units
+# =============================================================================
+components:
+ - id: turtlebot3-base
+ name: "TurtleBot3 Base"
+ type: "platform"
+ description: "TurtleBot3 Burger mobile robot platform"
+ area: robot
+
+ - id: lidar-sensor
+ name: "LiDAR Sensor"
+ type: "sensor"
+ description: "360° 2D LiDAR scanner"
+ area: robot
+
+ - id: nav2-stack
+ name: "Nav2 Stack"
+ type: "controller"
+ description: "Navigation 2 autonomous navigation stack"
+ area: navigation
+
+ - id: gateway
+ name: "SOVD Gateway"
+ type: "controller"
+ description: "ros2_medkit REST API gateway"
+ area: diagnostics
+
+ - id: fault-manager
+ name: "Fault Manager"
+ type: "controller"
+ description: "ros2_medkit fault management service"
+ area: diagnostics
+
+ - id: diagnostic-bridge-unit
+ name: "Diagnostic Bridge"
+ type: "controller"
+ description: "Bridges legacy /diagnostics topic to fault manager"
+ area: bridge
+
+# =============================================================================
+# APPS - ROS 2 nodes with runtime binding
+# =============================================================================
+apps:
+ # === Robot Apps ===
+ - id: turtlebot3-node
+ name: "TurtleBot3 Node"
+ category: "driver"
+ is_located_on: turtlebot3-base
+ description: "Main TurtleBot3 hardware interface"
+ ros_binding:
+ node_name: turtlebot3_node
+ namespace: /
+
+ - id: robot-state-publisher
+ name: "Robot State Publisher"
+ category: "driver"
+ is_located_on: turtlebot3-base
+ description: "Publishes robot TF tree"
+ ros_binding:
+ node_name: robot_state_publisher
+ namespace: /
+
+ - id: gazebo
+ name: "Gazebo Simulator"
+ category: "simulation"
+ is_located_on: turtlebot3-base
+ description: "Gazebo physics simulation"
+ ros_binding:
+ node_name: gazebo
+ namespace: /
+
+ # === Navigation Apps ===
+ - id: amcl
+ name: "AMCL Localization"
+ category: "localization"
+ is_located_on: nav2-stack
+ description: "Adaptive Monte Carlo Localization"
+ depends_on:
+ - turtlebot3-node
+ ros_binding:
+ node_name: amcl
+ namespace: /
+
+ - id: bt-navigator
+ name: "BT Navigator"
+ category: "navigation"
+ is_located_on: nav2-stack
+ description: "Behavior Tree Navigator"
+ ros_binding:
+ node_name: bt_navigator
+ namespace: /
+
+ - id: controller-server
+ name: "Controller Server"
+ category: "navigation"
+ is_located_on: nav2-stack
+ description: "Path following controller"
+ ros_binding:
+ node_name: controller_server
+ namespace: /
+
+ - id: planner-server
+ name: "Planner Server"
+ category: "navigation"
+ is_located_on: nav2-stack
+ description: "Global path planner"
+ ros_binding:
+ node_name: planner_server
+ namespace: /
+
+ - id: velocity-smoother
+ name: "Velocity Smoother"
+ category: "navigation"
+ is_located_on: nav2-stack
+ description: "Velocity command smoother"
+ ros_binding:
+ node_name: velocity_smoother
+ namespace: /
+
+ # === Diagnostics Apps ===
+ - id: medkit-gateway
+ name: "ros2_medkit Gateway"
+ category: "gateway"
+ is_located_on: gateway
+ description: "REST API gateway for SOVD protocol"
+ ros_binding:
+ node_name: ros2_medkit_gateway
+ namespace: /diagnostics
+
+ - id: medkit-fault-manager
+ name: "Fault Manager"
+ category: "diagnostics"
+ is_located_on: fault-manager
+ description: "Manages and stores fault information"
+ ros_binding:
+ node_name: fault_manager
+ namespace: /
+
+ - id: diagnostic-bridge
+ name: "Diagnostic Bridge"
+ category: "diagnostics"
+ is_located_on: diagnostic-bridge-unit
+ description: "Bridges /diagnostics topic (DiagnosticArray) to fault_manager"
+ ros_binding:
+ node_name: diagnostic_bridge
+ namespace: /bridge
+
+# =============================================================================
+# FUNCTIONS - High-level capabilities
+# =============================================================================
+functions:
+ - id: autonomous-navigation
+ name: "Autonomous Navigation"
+ category: "mobility"
+ description: "Navigate to goal positions autonomously"
+ hosted_by:
+ - amcl
+ - bt-navigator
+ - controller-server
+ - planner-server
+
+ - id: robot-control
+ name: "Robot Control"
+ category: "mobility"
+ description: "Basic robot movement and velocity control"
+ hosted_by:
+ - turtlebot3-node
+ - velocity-smoother
+
+ - id: fault-management
+ name: "Fault Management"
+ category: "diagnostics"
+ description: "Collect and expose fault information via SOVD API"
+ hosted_by:
+ - medkit-gateway
+ - medkit-fault-manager
+ - diagnostic-bridge
diff --git a/demos/turtlebot3_integration/docker-compose.yml b/demos/turtlebot3_integration/docker-compose.yml
index 92aae76..94352aa 100644
--- a/demos/turtlebot3_integration/docker-compose.yml
+++ b/demos/turtlebot3_integration/docker-compose.yml
@@ -9,6 +9,7 @@ services:
- DISPLAY=${DISPLAY}
- TURTLEBOT3_MODEL=burger
- ROS_DOMAIN_ID=30
+ - HEADLESS=${HEADLESS:-false}
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:rw
ports:
@@ -19,7 +20,7 @@ services:
bash -c "source /opt/ros/jazzy/setup.bash &&
source /root/demo_ws/install/setup.bash &&
export TURTLEBOT3_MODEL=burger &&
- ros2 launch turtlebot3_medkit_demo demo.launch.py"
+ ros2 launch turtlebot3_medkit_demo demo.launch.py headless:=$${HEADLESS}"
# NVIDIA GPU accelerated version
# Use with: docker compose --profile nvidia up
@@ -36,6 +37,7 @@ services:
- DISPLAY=${DISPLAY}
- TURTLEBOT3_MODEL=burger
- ROS_DOMAIN_ID=30
+ - HEADLESS=${HEADLESS:-false}
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
volumes:
@@ -55,12 +57,10 @@ services:
bash -c "source /opt/ros/jazzy/setup.bash &&
source /root/demo_ws/install/setup.bash &&
export TURTLEBOT3_MODEL=burger &&
- ros2 launch turtlebot3_medkit_demo demo.launch.py"
+ ros2 launch turtlebot3_medkit_demo demo.launch.py headless:=$${HEADLESS}"
sovd-web-ui:
- build:
- context: https://github.com/selfpatch/sovd_web_ui.git
- dockerfile: Dockerfile
+ image: ghcr.io/selfpatch/sovd_web_ui:latest
container_name: sovd_web_ui
ports:
- "3000:80"
diff --git a/demos/turtlebot3_integration/inject-collision.sh b/demos/turtlebot3_integration/inject-collision.sh
new file mode 100755
index 0000000..23fcb6c
--- /dev/null
+++ b/demos/turtlebot3_integration/inject-collision.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Inject Collision Warning - Send robot toward obstacle
+# This will trigger collision avoidance and potential path replanning
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "💥 Injecting COLLISION WARNING fault..."
+echo " Sending robot toward known obstacle location in turtlebot3_world"
+echo ""
+
+# Check for jq dependency
+if ! command -v jq >/dev/null 2>&1; then
+ echo "❌ 'jq' is required but not installed."
+ echo " Please install jq (e.g., 'sudo apt-get install jq') and retry."
+ exit 1
+fi
+
+# Check gateway
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ exit 1
+fi
+
+# In turtlebot3_world, there are obstacles around the center
+# Send goal that requires navigating close to obstacles
+echo "Sending navigation goal to obstacle-dense area..."
+RESPONSE=$(curl -s -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "request": {
+ "pose": {
+ "header": {"frame_id": "map"},
+ "pose": {
+ "position": {"x": 0.0, "y": 0.0, "z": 0.0},
+ "orientation": {"w": 1.0}
+ }
+ }
+ }
+ }')
+
+echo "$RESPONSE" | jq '.' 2>/dev/null || echo "$RESPONSE"
+
+echo ""
+echo "✓ Collision scenario triggered!"
+echo ""
+echo "Expected faults (via diagnostic_bridge):"
+echo " - COLLISION_MONITOR: Obstacle detected"
+echo " - CONTROLLER_SERVER: Path blocked"
+echo ""
+echo "The collision monitor should stop the robot if it gets too close."
+echo "Check faults with: curl ${API_BASE}/faults | jq"
diff --git a/demos/turtlebot3_integration/inject-controller-failure.sh b/demos/turtlebot3_integration/inject-controller-failure.sh
new file mode 100755
index 0000000..cb7ff98
--- /dev/null
+++ b/demos/turtlebot3_integration/inject-controller-failure.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Inject Controller Failure - Set very restrictive velocity limits
+# This will cause the controller to struggle following paths
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "🎮 Injecting CONTROLLER FAILURE fault..."
+echo " Setting very restrictive velocity limits (robot will move very slowly)"
+echo ""
+
+# Check gateway
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ exit 1
+fi
+
+# Set very low max velocity on velocity_smoother
+echo "Setting velocity_smoother max_velocity to 0.05 m/s (very slow)..."
+curl -s -X PUT "${API_BASE}/apps/velocity-smoother/configurations/max_velocity" \
+ -H "Content-Type: application/json" \
+ -d '{"value": [0.05, 0.0, 0.3]}'
+
+echo ""
+
+# Also reduce controller's max velocity
+echo "Setting controller_server FollowPath.max_vel_x to 0.05 m/s..."
+curl -s -X PUT "${API_BASE}/apps/controller-server/configurations/FollowPath.max_vel_x" \
+ -H "Content-Type: application/json" \
+ -d '{"value": 0.05}'
+
+echo ""
+echo "✓ Controller restriction injected!"
+echo ""
+echo "The robot will now move extremely slowly."
+echo "Expected faults (via diagnostic_bridge):"
+echo " - VELOCITY_SMOOTHER: Velocity limited"
+echo " - CONTROLLER_SERVER: Path following degraded"
+echo ""
+echo "Restore with: ./restore-normal.sh"
+echo "Check faults with: curl ${API_BASE}/faults | jq"
diff --git a/demos/turtlebot3_integration/inject-localization-failure.sh b/demos/turtlebot3_integration/inject-localization-failure.sh
new file mode 100755
index 0000000..fbe66e8
--- /dev/null
+++ b/demos/turtlebot3_integration/inject-localization-failure.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Inject Localization Failure - Reset AMCL with bad initial pose
+# This will cause localization issues and potential navigation failures
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "📍 Injecting LOCALIZATION FAILURE fault..."
+echo " Reinitializing AMCL with incorrect pose (robot thinks it's somewhere else)"
+echo ""
+
+# Check gateway
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ exit 1
+fi
+
+# Reinitialize global localization - this scatters particles and causes uncertainty
+echo "Triggering global localization reinitialize..."
+curl -s -X POST "${API_BASE}/apps/amcl/operations/reinitialize_global_localization/executions" \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+echo ""
+echo "✓ Localization failure injected!"
+echo ""
+echo "AMCL will now have high uncertainty until it re-localizes."
+echo "Expected faults (via diagnostic_bridge):"
+echo " - AMCL: Localization confidence low"
+echo " - BT_NAVIGATOR: Goal may fail due to uncertain pose"
+echo ""
+echo "Watch localization recover with:"
+echo " curl ${API_BASE}/apps/amcl/data/particlecloud | jq '.poses | length'"
+echo ""
+echo "Check faults with: curl ${API_BASE}/faults | jq"
diff --git a/demos/turtlebot3_integration/inject-nav-failure.sh b/demos/turtlebot3_integration/inject-nav-failure.sh
new file mode 100755
index 0000000..22a7cac
--- /dev/null
+++ b/demos/turtlebot3_integration/inject-nav-failure.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+# Inject Navigation Failure - Send goal to unreachable location
+# This will trigger a path planning failure from Nav2
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "🚫 Injecting NAVIGATION FAILURE fault..."
+echo " Sending goal to unreachable location (outside map bounds)"
+echo ""
+
+# Check for jq dependency
+if ! command -v jq >/dev/null 2>&1; then
+ echo "❌ 'jq' is required but not installed."
+ echo " Please install jq (e.g., 'sudo apt-get install jq') and retry."
+ exit 1
+fi
+
+# Check gateway
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ exit 1
+fi
+
+# Send goal to location far outside the map (turtlebot3_world is small ~5x5m)
+echo "Sending navigation goal to (100.0, 100.0) - far outside map..."
+RESPONSE=$(curl -s -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "request": {
+ "pose": {
+ "header": {"frame_id": "map"},
+ "pose": {
+ "position": {"x": 100.0, "y": 100.0, "z": 0.0},
+ "orientation": {"w": 1.0}
+ }
+ }
+ }
+ }')
+
+echo "$RESPONSE" | jq '.' 2>/dev/null || echo "$RESPONSE"
+
+echo ""
+echo "✓ Navigation failure injected!"
+echo ""
+echo "Expected faults (via diagnostic_bridge):"
+echo " - BT_NAVIGATOR: Goal rejected or path planning failed"
+echo " - PLANNER_SERVER: No valid path to goal"
+echo ""
+echo "Check faults with: curl ${API_BASE}/faults | jq"
diff --git a/demos/turtlebot3_integration/launch/demo.launch.py b/demos/turtlebot3_integration/launch/demo.launch.py
index 12ff013..a7e17c5 100644
--- a/demos/turtlebot3_integration/launch/demo.launch.py
+++ b/demos/turtlebot3_integration/launch/demo.launch.py
@@ -3,6 +3,8 @@
This launch file demonstrates ros2_medkit's hierarchical discovery by:
- Running TurtleBot3 + Nav2 (in root namespace)
- Adding ros2_medkit gateway under /diagnostics namespace
+ - Running fault_manager for fault aggregation
+ - Running diagnostic_bridge for legacy /diagnostics support
- Showing how nodes are organized into Areas based on namespaces
"""
@@ -11,10 +13,12 @@
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import (
+ AppendEnvironmentVariable,
DeclareLaunchArgument,
IncludeLaunchDescription,
SetEnvironmentVariable,
)
+from launch.conditions import IfCondition, UnlessCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
@@ -23,6 +27,7 @@
def generate_launch_description():
# Get package directories
turtlebot3_gazebo_dir = get_package_share_directory("turtlebot3_gazebo")
+ ros_gz_sim_dir = get_package_share_directory("ros_gz_sim")
nav2_bringup_dir = get_package_share_directory("nav2_bringup")
demo_pkg_dir = get_package_share_directory("turtlebot3_medkit_demo")
@@ -30,9 +35,22 @@ def generate_launch_description():
medkit_params_file = os.path.join(demo_pkg_dir, "config", "medkit_params.yaml")
nav2_params_file = os.path.join(demo_pkg_dir, "config", "nav2_params.yaml")
map_file = os.path.join(demo_pkg_dir, "config", "turtlebot3_world.yaml")
+ manifest_file = os.path.join(demo_pkg_dir, "config", "turtlebot3_manifest.yaml")
+
+ # Gazebo world file
+ world_file = os.path.join(turtlebot3_gazebo_dir, "worlds", "turtlebot3_world.world")
# Launch configuration variables
use_sim_time = LaunchConfiguration("use_sim_time", default="True")
+ headless = LaunchConfiguration("headless", default="False")
+ x_pose = LaunchConfiguration("x_pose", default="-2.0")
+ y_pose = LaunchConfiguration("y_pose", default="-0.5")
+
+ # Gazebo model path
+ set_gz_model_path = AppendEnvironmentVariable(
+ "GZ_SIM_RESOURCE_PATH",
+ os.path.join(turtlebot3_gazebo_dir, "models"),
+ )
return LaunchDescription(
[
@@ -42,20 +60,61 @@ def generate_launch_description():
default_value="True",
description="Use simulation (Gazebo) clock if true",
),
+ DeclareLaunchArgument(
+ "headless",
+ default_value="False",
+ description="Run Gazebo without GUI (headless=True for Docker/CI, False for local GUI)",
+ ),
+ DeclareLaunchArgument(
+ "x_pose",
+ default_value="-2.0",
+ description="Robot initial X position",
+ ),
+ DeclareLaunchArgument(
+ "y_pose",
+ default_value="-0.5",
+ description="Robot initial Y position",
+ ),
# Set TurtleBot3 model (can be overridden by environment variable)
SetEnvironmentVariable(
name="TURTLEBOT3_MODEL",
value=os.environ.get("TURTLEBOT3_MODEL", "burger"),
),
- # Launch TurtleBot3 Gazebo simulation (turtlebot3_world)
- # Runs in root namespace to publish standard topics (/scan, /odom, /cmd_vel)
+ set_gz_model_path,
+ # === HEADLESS MODE: Gazebo server only ===
+ IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ os.path.join(ros_gz_sim_dir, "launch", "gz_sim.launch.py")
+ ),
+ launch_arguments={
+ "gz_args": ["-r", "-s", "-v2", world_file],
+ "on_exit_shutdown": "true",
+ }.items(),
+ condition=IfCondition(headless),
+ ),
+ # === GUI MODE: Full TurtleBot3 world (server + client) ===
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
- os.path.join(
- turtlebot3_gazebo_dir, "launch", "turtlebot3_world.launch.py"
- )
+ os.path.join(turtlebot3_gazebo_dir, "launch", "turtlebot3_world.launch.py")
),
launch_arguments={"use_sim_time": use_sim_time}.items(),
+ condition=UnlessCondition(headless),
+ ),
+ # Spawn TurtleBot3 robot (headless mode only - GUI mode already includes spawn)
+ IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ os.path.join(turtlebot3_gazebo_dir, "launch", "spawn_turtlebot3.launch.py")
+ ),
+ launch_arguments={"x_pose": x_pose, "y_pose": y_pose}.items(),
+ condition=IfCondition(headless),
+ ),
+ # Robot state publisher (headless mode only - GUI mode includes it)
+ IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ os.path.join(turtlebot3_gazebo_dir, "launch", "robot_state_publisher.launch.py")
+ ),
+ launch_arguments={"use_sim_time": use_sim_time}.items(),
+ condition=IfCondition(headless),
),
# Launch Nav2 navigation stack
# Runs in root namespace to subscribe to robot topics
@@ -70,6 +129,26 @@ def generate_launch_description():
"autostart": "True",
}.items(),
),
+ # Launch ros2_medkit fault_manager in root namespace
+ # Aggregates faults from all nodes via ReportFault service
+ Node(
+ package="ros2_medkit_fault_manager",
+ executable="fault_manager_node",
+ name="fault_manager",
+ namespace="",
+ output="screen",
+ parameters=[{"use_sim_time": use_sim_time}],
+ ),
+ # Launch diagnostic_bridge under /bridge namespace
+ # Converts legacy /diagnostics topic to faults
+ Node(
+ package="ros2_medkit_diagnostic_bridge",
+ executable="diagnostic_bridge_node",
+ name="diagnostic_bridge",
+ namespace="bridge",
+ output="screen",
+ parameters=[{"use_sim_time": use_sim_time}],
+ ),
# Launch ros2_medkit gateway under /diagnostics namespace
# This demonstrates namespace-based Area organization in discovery
Node(
@@ -78,7 +157,13 @@ def generate_launch_description():
name="ros2_medkit_gateway",
namespace="diagnostics",
output="screen",
- parameters=[medkit_params_file, {"use_sim_time": use_sim_time}],
+ parameters=[
+ medkit_params_file,
+ {
+ "use_sim_time": use_sim_time,
+ "manifest_path": manifest_file,
+ },
+ ],
),
]
)
diff --git a/demos/turtlebot3_integration/package.xml b/demos/turtlebot3_integration/package.xml
index 4bffd0a..15fa18e 100644
--- a/demos/turtlebot3_integration/package.xml
+++ b/demos/turtlebot3_integration/package.xml
@@ -4,7 +4,7 @@
turtlebot3_medkit_demo
0.1.0
TurtleBot3 + ros2_medkit integration demo with Nav2 navigation
- Demo Maintainer
+ bburda
Apache-2.0
ament_cmake
@@ -12,6 +12,8 @@
ros2launch
turtlebot3_gazebo
ros2_medkit_gateway
+ ros2_medkit_fault_manager
+ ros2_medkit_diagnostic_bridge
nav2_bringup
nav2_bt_navigator
nav2_controller
diff --git a/demos/turtlebot3_integration/restore-normal.sh b/demos/turtlebot3_integration/restore-normal.sh
new file mode 100755
index 0000000..5c2c2dd
--- /dev/null
+++ b/demos/turtlebot3_integration/restore-normal.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# Restore Normal Operation - Reset all parameters and clear faults
+# Use this after running any inject-*.sh script
+
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
+
+echo "🔄 Restoring NORMAL operation..."
+echo ""
+
+# Check for jq dependency
+if ! command -v jq >/dev/null 2>&1; then
+ echo "❌ 'jq' is required but not installed."
+ echo " Please install jq (e.g., 'sudo apt-get install jq') and retry."
+ exit 1
+fi
+
+# Check gateway
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ exit 1
+fi
+
+# Cancel any active navigation goals
+echo "Canceling any active navigation goals..."
+# List active executions and cancel them
+EXECUTIONS=$(curl -s "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" 2>/dev/null)
+if echo "$EXECUTIONS" | jq -e '.items[]' > /dev/null 2>&1; then
+ echo "$EXECUTIONS" | jq -r '.items[].id' | while read -r EXEC_ID; do
+ if [ -n "$EXEC_ID" ]; then
+ curl -s -X DELETE "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions/$EXEC_ID" > /dev/null 2>&1
+ echo " Canceled execution: $EXEC_ID"
+ fi
+ done
+fi
+
+# Restore velocity smoother defaults
+echo "Restoring velocity_smoother defaults..."
+curl -s -X PUT "${API_BASE}/apps/velocity-smoother/configurations/max_velocity" \
+ -H "Content-Type: application/json" \
+ -d '{"value": [0.26, 0.0, 1.0]}' > /dev/null
+
+# Restore controller defaults
+echo "Restoring controller_server defaults..."
+curl -s -X PUT "${API_BASE}/apps/controller-server/configurations/FollowPath.max_vel_x" \
+ -H "Content-Type: application/json" \
+ -d '{"value": 0.26}' > /dev/null
+
+# Clear all faults
+echo ""
+echo "Clearing all faults from FaultManager..."
+curl -s -X DELETE "${API_BASE}/faults" > /dev/null
+
+echo ""
+echo "✓ Normal operation restored!"
+echo ""
+echo "Current fault status:"
+curl -s "${API_BASE}/faults" | jq '.items | length' | xargs -I {} echo " Active faults: {}"
+echo ""
+echo "Robot is ready for normal operation."
diff --git a/demos/turtlebot3_integration/run-demo.sh b/demos/turtlebot3_integration/run-demo.sh
index 64d04bc..4ddadf8 100755
--- a/demos/turtlebot3_integration/run-demo.sh
+++ b/demos/turtlebot3_integration/run-demo.sh
@@ -40,6 +40,7 @@ trap cleanup EXIT
# Parse arguments
COMPOSE_ARGS=""
BUILD_ARGS=""
+HEADLESS_MODE="false"
usage() {
echo "Usage: $0 [OPTIONS]"
@@ -47,13 +48,17 @@ usage() {
echo "Options:"
echo " --nvidia Use NVIDIA GPU acceleration"
echo " --no-cache Build Docker images without cache"
+ echo " --headless Run without Gazebo GUI (default: GUI enabled)"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
- echo " $0 # CPU-only mode"
- echo " $0 --nvidia # With GPU acceleration"
+ echo " $0 # With Gazebo GUI (default)"
+ echo " $0 --headless # Headless mode (no GUI)"
+ echo " $0 --nvidia # GPU acceleration + GUI"
echo " $0 --no-cache # Rebuild without cache"
- echo " $0 --nvidia --no-cache # Both options"
+ echo ""
+ echo "Environment variables:"
+ echo " HEADLESS=true|false Control GUI mode (default: false)"
}
while [[ $# -gt 0 ]]; do
@@ -66,6 +71,10 @@ while [[ $# -gt 0 ]]; do
echo "Building without cache"
BUILD_ARGS="--no-cache"
;;
+ --headless)
+ echo "Running in headless mode (no GUI)"
+ HEADLESS_MODE="true"
+ ;;
-h|--help)
usage
exit 0
@@ -83,6 +92,10 @@ if [[ -z "$COMPOSE_ARGS" ]]; then
echo "Using CPU-only mode (use --nvidia flag for GPU acceleration)"
fi
+# Export HEADLESS mode for docker-compose
+export HEADLESS=$HEADLESS_MODE
+echo "Gazebo mode: $([ "$HEADLESS_MODE" = "true" ] && echo "headless (no GUI)" || echo "GUI enabled")"
+
# Build and run
echo " Building and starting demo..."
echo " (First run takes ~5-10 min, downloading ~4GB image)"
diff --git a/demos/turtlebot3_integration/send-nav-goal.sh b/demos/turtlebot3_integration/send-nav-goal.sh
index cb0dcfa..4335e5a 100755
--- a/demos/turtlebot3_integration/send-nav-goal.sh
+++ b/demos/turtlebot3_integration/send-nav-goal.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-# Send a navigation goal to the TurtleBot3 robot
+# Send a navigation goal to the TurtleBot3 robot via SOVD API
# Usage: ./send-nav-goal.sh [x] [y] [yaw]
# x - target x position (default: 2.0)
# y - target y position (default: 0.5)
@@ -7,7 +7,8 @@
set -e
-CONTAINER_NAME="turtlebot3_medkit_demo"
+GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
+API_BASE="${GATEWAY_URL}/api/v1"
# Check for required dependencies
if ! command -v bc &> /dev/null; then
@@ -15,6 +16,11 @@ if ! command -v bc &> /dev/null; then
exit 1
fi
+if ! command -v jq &> /dev/null; then
+ echo "❌ Error: 'jq' command not found. Please install jq (e.g., 'apt-get install jq')"
+ exit 1
+fi
+
# Default goal position
X=${1:-2.0}
Y=${2:-0.5}
@@ -34,28 +40,61 @@ validate_numeric "$X" "x"
validate_numeric "$Y" "y"
validate_numeric "$YAW" "yaw"
+# Check gateway is available
+echo "Checking gateway..."
+if ! curl -sf "${API_BASE}/health" > /dev/null 2>&1; then
+ echo "❌ Gateway not available at ${GATEWAY_URL}"
+ echo " Start with: ./run-demo.sh"
+ exit 1
+fi
+echo "✓ Gateway is healthy"
+
# Calculate quaternion from yaw (rotation around z-axis)
# Full quaternion: x=0, y=0, z=sin(yaw/2), w=cos(yaw/2)
W=$(echo "c($YAW/2)" | bc -l)
Z=$(echo "s($YAW/2)" | bc -l)
+echo ""
echo "🤖 Sending navigation goal to TurtleBot3"
echo " Target: x=$X, y=$Y, yaw=$YAW rad"
echo " Quaternion: x=0, y=0, z=$Z, w=$W"
echo ""
-# Check if container is running
-if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
- echo "❌ Container '$CONTAINER_NAME' is not running!"
- echo " Start with: ./run-demo.sh"
+# Create execution via SOVD API
+# bt_navigator exposes NavigateToPose action as an operation
+RESPONSE=$(curl -s -X POST "${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"request\": {
+ \"pose\": {
+ \"header\": {\"frame_id\": \"map\"},
+ \"pose\": {
+ \"position\": {\"x\": $X, \"y\": $Y, \"z\": 0.0},
+ \"orientation\": {\"x\": 0.0, \"y\": 0.0, \"z\": $Z, \"w\": $W}
+ }
+ }
+ }
+ }")
+
+# Check for errors
+if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
+ echo "❌ Navigation goal failed:"
+ echo "$RESPONSE" | jq '.error'
exit 1
fi
-# Send the navigation goal
-# Using validated numeric values - safe to interpolate after validation
-docker exec -it "$CONTAINER_NAME" bash -c "
-source /opt/ros/jazzy/setup.bash && \
-source /root/demo_ws/install/setup.bash && \
-ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
- \"{pose: {header: {frame_id: 'map'}, pose: {position: {x: $X, y: $Y, z: 0.0}, orientation: {x: 0.0, y: 0.0, z: $Z, w: $W}}}}\"
-"
+# Extract execution ID
+EXEC_ID=$(echo "$RESPONSE" | jq -r '.execution_id // .id // empty')
+
+if [ -z "$EXEC_ID" ]; then
+ echo "✓ Navigation goal sent (synchronous response)"
+ echo "$RESPONSE" | jq '.'
+else
+ echo "✓ Navigation execution started: $EXEC_ID"
+ echo ""
+ echo "Check status with:"
+ echo " curl ${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions/$EXEC_ID | jq"
+ echo ""
+ echo "Cancel with:"
+ echo " curl -X DELETE ${API_BASE}/apps/bt-navigator/operations/navigate_to_pose/executions/$EXEC_ID"
+fi