From 8014283866aa593229d0c41aff77abfe045f8245 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Thu, 29 Jan 2026 16:22:23 +0000 Subject: [PATCH 1/3] feat(turtlebot3): Complete demo with manifest discovery, fault management and GUI as default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements to TurtleBot3 integration demo: Core Features: - Add manifest-based entity discovery (SOVD hierarchy: Areas→Components→Apps→Functions) - Implement fault management via diagnostic_bridge + fault_manager - Add SQLite storage for persistent fault tracking - Support conditional headless/GUI Gazebo modes Docker & Launch: - Fix Dockerfile: add ros2_medkit_fault_manager, fault_reporter, diagnostic_bridge packages - Add SQLite dependencies (sqlite3, libsqlite3-dev) - Update launch file: conditional Gazebo launch based on headless argument - Change default mode from headless to GUI for better UX - Add HEADLESS environment variable support in docker-compose.yml Manifest & Configuration: - Add turtlebot3_manifest.yaml defining complete entity hierarchy - Configure 4 areas: robot, navigation, diagnostics, bridge - Define 6 components: turtlebot3-base, lidar-sensor, nav2-stack, gateway, fault-manager, diagnostic-bridge-unit - Define 11 apps with ROS node bindings and dependencies - Define 3 functions: autonomous-navigation, robot-control, fault-management Shell Scripts: - Fix all app IDs: underscore format → hyphen format (bt_navigator → bt-navigator, etc.) - Add check-entities.sh: explore SOVD entity hierarchy - Add check-faults.sh: view active faults - Add inject-nav-failure.sh: trigger unreachable goal scenario - Add inject-localization-failure.sh: reset AMCL with high uncertainty - Add inject-controller-failure.sh: set restrictive velocity limits - Add inject-collision.sh: navigate toward obstacles - Add restore-normal.sh: restore defaults and clear faults - Update send-nav-goal.sh: use SOVD API instead of docker exec Documentation: - Completely rewrite TurtleBot3 demo README with: * Clear GUI vs headless mode instructions * SOVD entity hierarchy explanation * Fault management architecture diagram * Fault injection scenarios table * Complete REST API examples for all endpoints * Scripts reference table Testing: - All scripts verified working with GATEWAY_URL=http://172.19.0.2:8080 - Navigation goals successfully executed via SOVD API - Fault injection scenarios tested - 23 components discovered, 0 initial faults confirmed - GUI and headless modes both tested and working --- README.md | 104 +++++- demos/turtlebot3_integration/Dockerfile | 5 + demos/turtlebot3_integration/README.md | 350 ++++++++++++++---- .../turtlebot3_integration/check-entities.sh | 64 ++++ demos/turtlebot3_integration/check-faults.sh | 46 +++ .../config/medkit_params.yaml | 13 +- .../config/turtlebot3_manifest.yaml | 215 +++++++++++ .../turtlebot3_integration/docker-compose.yml | 12 +- .../inject-collision.sh | 45 +++ .../inject-controller-failure.sh | 41 ++ .../inject-localization-failure.sh | 35 ++ .../inject-nav-failure.sh | 43 +++ .../launch/demo.launch.py | 99 ++++- .../turtlebot3_integration/restore-normal.sh | 53 +++ demos/turtlebot3_integration/run-demo.sh | 19 +- demos/turtlebot3_integration/send-nav-goal.sh | 67 +++- 16 files changed, 1091 insertions(+), 120 deletions(-) create mode 100755 demos/turtlebot3_integration/check-entities.sh create mode 100755 demos/turtlebot3_integration/check-faults.sh create mode 100644 demos/turtlebot3_integration/config/turtlebot3_manifest.yaml create mode 100755 demos/turtlebot3_integration/inject-collision.sh create mode 100755 demos/turtlebot3_integration/inject-controller-failure.sh create mode 100755 demos/turtlebot3_integration/inject-localization-failure.sh create mode 100755 demos/turtlebot3_integration/inject-nav-failure.sh create mode 100755 demos/turtlebot3_integration/restore-normal.sh 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/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..379dfc6 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..187d748 --- /dev/null +++ b/demos/turtlebot3_integration/check-entities.sh @@ -0,0 +1,64 @@ +#!/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' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo_step() { + echo -e "\n${BLUE}=== $1 ===${NC}\n" +} + +echo "╔════════════════════════════════════════════════════════════╗" +echo "║ SOVD Entity Hierarchy Explorer ║" +echo "║ TurtleBot3 + Nav2 Demo ║" +echo "╚════════════════════════════════════════════════════════════╝" + +# 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..ce6d93d --- /dev/null +++ b/demos/turtlebot3_integration/check-faults.sh @@ -0,0 +1,46 @@ +#!/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 "" + +# 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..c47746f 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,12 @@ 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" + depends_on: + - turtlebot3-demo diff --git a/demos/turtlebot3_integration/inject-collision.sh b/demos/turtlebot3_integration/inject-collision.sh new file mode 100755 index 0000000..7de0943 --- /dev/null +++ b/demos/turtlebot3_integration/inject-collision.sh @@ -0,0 +1,45 @@ +#!/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 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..4c53ab1 --- /dev/null +++ b/demos/turtlebot3_integration/inject-nav-failure.sh @@ -0,0 +1,43 @@ +#!/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 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..a2fbd5b 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,18 +13,21 @@ 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.substitutions import LaunchConfiguration, PythonExpression from launch_ros.actions import Node 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 (both modes) + 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/restore-normal.sh b/demos/turtlebot3_integration/restore-normal.sh new file mode 100755 index 0000000..052e077 --- /dev/null +++ b/demos/turtlebot3_integration/restore-normal.sh @@ -0,0 +1,53 @@ +#!/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 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 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 2>&1 + +# 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 2>&1 + +# Clear all faults +echo "" +echo "Clearing all faults from FaultManager..." +curl -s -X DELETE "${API_BASE}/faults" > /dev/null 2>&1 + +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 From 1ba48f4aedc52b0c9230cb1e6a23bf93f432e4a6 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Fri, 30 Jan 2026 18:28:28 +0000 Subject: [PATCH 2/3] fix(turtlebot3): Address PR review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix app IDs in README (underscore → hyphen to match manifest) - Add jq dependency check to shell scripts - Remove depends_on from sovd-web-ui - Remove unused PythonExpression import in launch file - Fix misleading comment about spawn (headless mode only) - Fix gz_args list construction for clarity - Add missing exec_depend in package.xml - Keep stderr visible in restore-normal.sh curl commands --- demos/sensor_diagnostics/package.xml | 2 +- demos/turtlebot3_integration/README.md | 12 ++++++------ demos/turtlebot3_integration/check-entities.sh | 7 +++++++ demos/turtlebot3_integration/check-faults.sh | 7 +++++++ demos/turtlebot3_integration/docker-compose.yml | 2 -- demos/turtlebot3_integration/inject-collision.sh | 7 +++++++ demos/turtlebot3_integration/inject-nav-failure.sh | 7 +++++++ demos/turtlebot3_integration/launch/demo.launch.py | 6 +++--- demos/turtlebot3_integration/package.xml | 4 +++- demos/turtlebot3_integration/restore-normal.sh | 13 ++++++++++--- 10 files changed, 51 insertions(+), 16 deletions(-) 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/README.md b/demos/turtlebot3_integration/README.md index 379dfc6..eac671b 100644 --- a/demos/turtlebot3_integration/README.md +++ b/demos/turtlebot3_integration/README.md @@ -106,12 +106,12 @@ docker compose --profile nvidia up --build ```bash # Send velocity command using Apps data endpoint (moves robot forward) -curl -X PUT http://localhost:8080/api/v1/apps/turtlebot3_node/data/cmd_vel \ +curl -X PUT http://localhost:8080/api/v1/apps/turtlebot3-node/data/cmd_vel \ -H "Content-Type: application/json" \ -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 PUT http://localhost:8080/api/v1/apps/turtlebot3_node/data/cmd_vel \ +curl -X PUT http://localhost:8080/api/v1/apps/turtlebot3-node/data/cmd_vel \ -H "Content-Type: application/json" \ -d '{"linear": {"x": 0.0}, "angular": {"z": 0.0}}' ``` @@ -153,20 +153,20 @@ curl http://localhost:8080/api/v1/apps/amcl | jq ```bash # Get LiDAR scan data -curl http://localhost:8080/api/v1/apps/turtlebot3_node/data/scan | jq '{ +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 '{ +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 +curl http://localhost:8080/api/v1/apps/turtlebot3-node/data | jq ``` ### Fault Management @@ -179,7 +179,7 @@ curl http://localhost:8080/api/v1/faults | jq curl http://localhost:8080/api/v1/areas/robot/faults | jq # Clear a specific fault -curl -X DELETE http://localhost:8080/api/v1/apps/diagnostic_bridge/faults/TURTLEBOT3_NODE +curl -X DELETE http://localhost:8080/api/v1/apps/diagnostic-bridge/faults/TURTLEBOT3_NODE ``` ### Operations (Service Calls) diff --git a/demos/turtlebot3_integration/check-entities.sh b/demos/turtlebot3_integration/check-entities.sh index 187d748..b6095b9 100755 --- a/demos/turtlebot3_integration/check-entities.sh +++ b/demos/turtlebot3_integration/check-entities.sh @@ -20,6 +20,13 @@ 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..." diff --git a/demos/turtlebot3_integration/check-faults.sh b/demos/turtlebot3_integration/check-faults.sh index ce6d93d..944ed6d 100755 --- a/demos/turtlebot3_integration/check-faults.sh +++ b/demos/turtlebot3_integration/check-faults.sh @@ -8,6 +8,13 @@ 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 diff --git a/demos/turtlebot3_integration/docker-compose.yml b/demos/turtlebot3_integration/docker-compose.yml index c47746f..94352aa 100644 --- a/demos/turtlebot3_integration/docker-compose.yml +++ b/demos/turtlebot3_integration/docker-compose.yml @@ -64,5 +64,3 @@ services: container_name: sovd_web_ui ports: - "3000:80" - depends_on: - - turtlebot3-demo diff --git a/demos/turtlebot3_integration/inject-collision.sh b/demos/turtlebot3_integration/inject-collision.sh index 7de0943..23fcb6c 100755 --- a/demos/turtlebot3_integration/inject-collision.sh +++ b/demos/turtlebot3_integration/inject-collision.sh @@ -9,6 +9,13 @@ 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}" diff --git a/demos/turtlebot3_integration/inject-nav-failure.sh b/demos/turtlebot3_integration/inject-nav-failure.sh index 4c53ab1..22a7cac 100755 --- a/demos/turtlebot3_integration/inject-nav-failure.sh +++ b/demos/turtlebot3_integration/inject-nav-failure.sh @@ -9,6 +9,13 @@ 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}" diff --git a/demos/turtlebot3_integration/launch/demo.launch.py b/demos/turtlebot3_integration/launch/demo.launch.py index a2fbd5b..a7e17c5 100644 --- a/demos/turtlebot3_integration/launch/demo.launch.py +++ b/demos/turtlebot3_integration/launch/demo.launch.py @@ -20,7 +20,7 @@ ) from launch.conditions import IfCondition, UnlessCondition from launch.launch_description_sources import PythonLaunchDescriptionSource -from launch.substitutions import LaunchConfiguration, PythonExpression +from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node @@ -87,7 +87,7 @@ def generate_launch_description(): os.path.join(ros_gz_sim_dir, "launch", "gz_sim.launch.py") ), launch_arguments={ - "gz_args": ["-r -s -v2 ", world_file], + "gz_args": ["-r", "-s", "-v2", world_file], "on_exit_shutdown": "true", }.items(), condition=IfCondition(headless), @@ -100,7 +100,7 @@ def generate_launch_description(): launch_arguments={"use_sim_time": use_sim_time}.items(), condition=UnlessCondition(headless), ), - # Spawn TurtleBot3 robot (both modes) + # Spawn TurtleBot3 robot (headless mode only - GUI mode already includes spawn) IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(turtlebot3_gazebo_dir, "launch", "spawn_turtlebot3.launch.py") 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 index 052e077..4b4a080 100755 --- a/demos/turtlebot3_integration/restore-normal.sh +++ b/demos/turtlebot3_integration/restore-normal.sh @@ -8,6 +8,13 @@ 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}" @@ -31,18 +38,18 @@ fi 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 2>&1 + -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 2>&1 + -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 2>&1 +curl -s -X DELETE "${API_BASE}/faults" > /dev/null echo "" echo "✓ Normal operation restored!" From f36aba6584efb3ff9196a0b7df11510bbc37dd36 Mon Sep 17 00:00:00 2001 From: Bartosz Burda Date: Fri, 30 Jan 2026 18:50:39 +0000 Subject: [PATCH 3/3] fix(turtlebot3): Fix shellcheck issues - Remove unused YELLOW variable from check-entities.sh - Add -r flag to read command in restore-normal.sh to handle backslashes properly --- demos/turtlebot3_integration/check-entities.sh | 1 - demos/turtlebot3_integration/restore-normal.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/demos/turtlebot3_integration/check-entities.sh b/demos/turtlebot3_integration/check-entities.sh index b6095b9..5b04968 100755 --- a/demos/turtlebot3_integration/check-entities.sh +++ b/demos/turtlebot3_integration/check-entities.sh @@ -8,7 +8,6 @@ API_BASE="${GATEWAY_URL}/api/v1" # Colors for output BLUE='\033[0;34m' GREEN='\033[0;32m' -YELLOW='\033[1;33m' NC='\033[0m' echo_step() { diff --git a/demos/turtlebot3_integration/restore-normal.sh b/demos/turtlebot3_integration/restore-normal.sh index 4b4a080..5c2c2dd 100755 --- a/demos/turtlebot3_integration/restore-normal.sh +++ b/demos/turtlebot3_integration/restore-normal.sh @@ -26,7 +26,7 @@ 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 EXEC_ID; do + 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"