Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
4cc3700
feat(demos/ota): scaffold pack_artifact CLI
bburda Apr 26, 2026
0a3ddaf
feat(demos/ota): pack_artifact argparse + dispatcher signature
bburda Apr 26, 2026
3a5b41e
feat(demos/ota): build SOVD-shaped catalog entry
bburda Apr 26, 2026
3d22b16
feat(demos/ota): merge_catalog with id-based replace
bburda Apr 26, 2026
876b960
feat(demos/ota): tarball creation from install dir
bburda Apr 26, 2026
f86c765
feat(demos/ota): pack_artifact end-to-end run() with kind dispatch
bburda Apr 26, 2026
161b707
fix(demos/ota): version default to 0.0.0 + cleanup unused symbols + p…
bburda Apr 26, 2026
398842b
test(demos/ota): cover colcon_build, install e2e, version-required guard
bburda Apr 26, 2026
216acc7
feat(demos/ota): ota_update_server scaffold
bburda Apr 26, 2026
e42af7e
test(ota_server): /catalog endpoint coverage
bburda Apr 26, 2026
ab10c29
test(ota_server): /artifacts endpoint + path traversal guard
bburda Apr 26, 2026
9b71e5a
feat(demos/ota): ota_update_server Dockerfile
bburda Apr 26, 2026
1f838de
chore(ota_server): pyright config to silence venv import warnings
bburda Apr 26, 2026
653902b
fix(ota_server): mark /artifacts route response_class=FileResponse
bburda Apr 26, 2026
248ef32
feat(demos/ota): broken_lidar node with phantom /scan return
bburda Apr 26, 2026
c8e10eb
feat(demos/ota): fixed_lidar (clean /scan, no phantom)
bburda Apr 26, 2026
a14243b
feat(demos/ota): broken_lidar_legacy do-nothing node (uninstall target)
bburda Apr 26, 2026
01e5fc8
feat(demos/ota): obstacle_classifier_v2 (install target, /scan -> /sa…
bburda Apr 26, 2026
9defc1a
feat(demos/ota): build_artifacts.sh + gitignore generated tarballs
bburda Apr 26, 2026
706c62c
fix(demos/ota): use array for pack_artifact invocation in build script
bburda Apr 26, 2026
1a9f20a
feat(demos/ota): ota_update_plugin C++ gateway plugin
bburda Apr 26, 2026
78816db
fix(ota_plugin): double-fork to avoid zombies, init catalog client in…
bburda Apr 26, 2026
3517bb8
feat(demos/ota): thread x_medkit_replaces_executable for update kind
bburda Apr 26, 2026
3bb6b1b
fix(ota_plugin): honor x_medkit_replaces_executable when killing old …
bburda Apr 26, 2026
f088c7e
feat(demos/ota): docker compose stack + gateway config + entrypoint +…
bburda Apr 26, 2026
2f7d817
fix(ota_plugin): __has_include compat for older gateway updates/ head…
bburda Apr 26, 2026
ec5070f
fix(ota_plugin): cmdline-based pgrep + UpdateProvider C export + runt…
bburda Apr 26, 2026
80e4af1
fix(demos/ota): use SOVD spec field names update_name + x_medkit_version
bburda Apr 26, 2026
8f48af1
test(demos/ota): committable Playwright e2e smoke driving web UI agai…
bburda Apr 26, 2026
a1726c4
feat(demos/ota): run-demo / stop-demo / check-demo / trigger-* scripts
bburda Apr 27, 2026
bd7a54f
test(demos/ota): smoke test + CI job mirroring sensor_diagnostics pat…
bburda Apr 27, 2026
5baa086
fix(demos/ota): rename unused loop var in run-demo to satisfy shellch…
bburda Apr 27, 2026
bf6d540
ci(ota): build artifacts inside ros:jazzy container instead of instal…
bburda Apr 27, 2026
e89628e
docs: list ota_nav2_sensor_fix in top-level README + smoke test catalog
bburda Apr 27, 2026
43a66e3
feat(demos/ota): bake foxglove_bridge into gateway image (port 8765)
bburda Apr 27, 2026
26e61f4
fix(demos/ota): launch fault_manager_node so /faults endpoint responds
bburda Apr 27, 2026
add3bb4
feat(demos/ota): bake TurtleBot3 + Nav2 + headless Gazebo into demo
bburda Apr 27, 2026
1dbabd4
docs(demos/ota): refresh README + run-demo for the TB3 / Nav2 / gz-si…
bburda Apr 27, 2026
d82fc94
test(demos/ota): add Uninstall + SetRemap smoke checks; fix flaky log…
bburda Apr 27, 2026
5067ff9
fix(demos/ota): drop runtime function auto-gen + pin gateway to logs-…
bburda Apr 28, 2026
08b0c21
feat(demos/ota): add SOVD manifest with logical functions
bburda Apr 28, 2026
dab79c9
feat(demos/ota): reactive fault narrative + reproducible artefact build
bburda Apr 29, 2026
7bd90be
feat(demos/ota): drop areas from manifest - components are the boundary
bburda Apr 29, 2026
6b9e084
refactor(demos/ota): collapse to one component, keep apps + functions
bburda Apr 29, 2026
ca0232d
fix(demos/ota): subscribe only TwistStamped on /cmd_vel
bburda Apr 29, 2026
602ad90
fix(demos/ota): move robot spawn into the map + revert phantom to sin…
bburda Apr 29, 2026
f48cbba
fix(demos/ota): broadcast map -> odom continuously when robot is idle
bburda Apr 29, 2026
17325d5
fix(demos/ota): bypass turtlebot3_gazebo's hardcoded frame_prefix slash
bburda Apr 29, 2026
af571b4
fix(ota_plugin): forward use_sim_time to OTA-spawned processes
bburda Apr 29, 2026
e7cb757
feat(ota_plugin): write manifest fragments + notify gateway on instal…
bburda Apr 29, 2026
d411a7b
ci(ota): drop "Build artifacts on host" step from narrative job
bburda Apr 29, 2026
041a7a6
fix(ota_demo): report fault source_id with leading slash so per-entit…
bburda Apr 29, 2026
21bc884
fix(ota_demo): spawn pose, foxglove_bridge depth, gateway upstream ref
bburda May 10, 2026
3419003
fix(ota_plugin): migrate includes to gateway core/* canonical paths
bburda May 26, 2026
fff9581
fix(tests/ota): use here-string to avoid SIGPIPE in gateway log asser…
bburda May 26, 2026
81b8317
fix(demos/lib): resolve sourced lib path via BASH_SOURCE instead of $0
bburda May 26, 2026
68fcde6
fix(demos/triggers): use hyphenated entity IDs to match gateway norma…
bburda May 26, 2026
b87e16b
fix(demos): relax nounset around ROS setup.bash sourcing
bburda May 26, 2026
5c9188d
fix(demos/tb3): treat nav2 action rejection as expected in inject-loc…
bburda May 26, 2026
39a0925
chore(demos): restore exec bit on injection/diagnostic scripts
bburda May 26, 2026
File filter

Filter by extension

Filter by extension


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

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

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

- name: Build and start OTA demo
working-directory: demos/ota_nav2_sensor_fix
run: docker compose up -d --build

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

- name: Show gateway logs on failure
if: failure()
working-directory: demos/ota_nav2_sensor_fix
run: docker compose logs gateway --tail=200

- name: Show update server logs on failure
if: failure()
working-directory: demos/ota_nav2_sensor_fix
run: docker compose logs ota_update_server --tail=200

- name: Teardown
if: always()
working-directory: demos/ota_nav2_sensor_fix
run: docker compose down

# Separate job from build-and-test-ota: this one publishes /goal_pose,
# waits for the controller to actually try to drive, asserts the
# reactive SCAN_PHANTOM_RETURN fault appears, and only then runs the
# OTA swap. Catches regressions in the demo narrative (broken_lidar
# subscribing /cmd_vel, fault_manager debounce, fixed_lidar not
# reporting). Slower than the API-only smoke job because it has to
# wait for nav2 lifecycle to settle and for /cmd_vel to actually fire,
# so it's split out and can fail in isolation without blocking the
# quick OTA-endpoint check.
ota-demo-narrative:
needs: lint
runs-on: ubuntu-24.04
steps:
- name: Show triggering source
if: github.event_name == 'repository_dispatch'
run: |
SHA="${{ github.event.client_payload.sha }}"
RUN_URL="${{ github.event.client_payload.run_url }}"
echo "## Triggered by ros2_medkit" >> "$GITHUB_STEP_SUMMARY"
echo "- Commit: \`${SHA:-unknown}\`" >> "$GITHUB_STEP_SUMMARY"
if [ -n "$RUN_URL" ]; then
echo "- Run: [View triggering run]($RUN_URL)" >> "$GITHUB_STEP_SUMMARY"
fi

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

- name: Build and start OTA demo
working-directory: demos/ota_nav2_sensor_fix
# docker compose up --build runs the multi-stage build for
# ota_update_server which produces the catalog + tarballs
# internally - no separate "build artifacts on host" step
# needed (and the host wouldn't have ros2_medkit_msgs anyway).
run: docker compose up -d --build

- name: Run demo narrative smoke
run: ./tests/smoke_test_demo_narrative.sh

- name: Show gateway logs on failure
if: failure()
working-directory: demos/ota_nav2_sensor_fix
run: docker compose logs gateway --tail=300

- name: Teardown
if: always()
working-directory: demos/ota_nav2_sensor_fix
run: docker compose down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ All demos support:
| [TurtleBot3 Integration](demos/turtlebot3_integration/) | Full ros2_medkit integration with TurtleBot3 and Nav2 | SOVD-compliant API, manifest-based discovery, fault management | ✅ Ready |
| [MoveIt Pick-and-Place](demos/moveit_pick_place/) | Panda 7-DOF arm with MoveIt 2 manipulation and ros2_medkit | Planning fault detection, controller monitoring, joint limits | ✅ Ready |
| [Multi-ECU Aggregation](demos/multi_ecu_aggregation/) | Multi-ECU peer aggregation with 3 ECUs (perception, planning, actuation), mDNS discovery, cross-ECU functions | Peer aggregation, mDNS discovery, cross-ECU functions | ✅ Ready |
| [OTA over SOVD - nav2 sensor fix](demos/ota_nav2_sensor_fix/) | Dev-grade OTA plugin showing the SOVD `/updates` lifecycle - update a broken lidar node, install a new safety classifier, uninstall a deprecated package | SOVD-spec update / install / uninstall, native binary swap, fork+exec process management, Foxglove panel + curl scripts | ✅ Ready |

### Quick Start

Expand Down Expand Up @@ -150,6 +151,32 @@ cd demos/multi_ecu_aggregation
- Unified SOVD-compliant REST API spanning all ECUs
- Web UI for browsing aggregated entity hierarchy

#### OTA over SOVD Demo (Dev-grade Update / Install / Uninstall)

End-to-end demo of the SOVD `/updates` resource: a broken lidar node is
swapped with a fixed version over HTTP, an extra safety classifier is
installed from scratch, and a deprecated package is uninstalled - all
without SSH, all spec-compliant.

```bash
cd demos/ota_nav2_sensor_fix
./run-demo.sh # build artifacts + bring up gateway/plugin/update server
./check-demo.sh # show registered updates + per-id status + live process state
./trigger-update.sh # broken_lidar -> fixed_lidar (the headline)
./trigger-install.sh # install obstacle_classifier_v2
./trigger-uninstall.sh # remove broken_lidar_legacy
./stop-demo.sh
```

**Features:**

- Dev-grade `ota_update_plugin` C++ gateway plugin (UpdateProvider + GatewayPlugin)
- SOVD ISO 17978-3 compliant `/updates` resource: kind derived from
`updated_components` / `added_components` / `removed_components` metadata
- Native binary swap + `fork+exec` process management (no containers, no signing)
- Foxglove Studio panel mirrors the same SOVD client patterns as the web UI
- Pairs with the [`ros2_medkit_foxglove_extension`](https://github.com/selfpatch/ros2_medkit_foxglove_extension) Updates panel

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -209,9 +236,11 @@ Each demo has automated smoke tests that verify the gateway starts and the REST
./tests/smoke_test.sh # Sensor diagnostics (full API coverage + fault injection + beacons)
./tests/smoke_test_turtlebot3.sh # TurtleBot3 (discovery, data, operations, scripts, triggers, logs)
./tests/smoke_test_moveit.sh # MoveIt pick-and-place (discovery, data, operations, scripts, triggers, logs)
./tests/smoke_test_multi_ecu.sh # Multi-ECU aggregation (per-ECU discovery + aggregated view)
./tests/smoke_test_ota.sh # OTA over SOVD (catalog, /updates spec shape, prepare/execute, process swap)
```

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

## Related Projects

Expand Down
Empty file modified demos/moveit_pick_place/arm-self-test.sh
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

echo "Injecting COLLISION fault..."
echo " Spawning surprise obstacle in robot workspace"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

echo "Injecting PLANNING FAILURE fault..."
echo " Adding collision wall between pick and place positions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

GATEWAY_URL="${GATEWAY_URL:-http://localhost:8080}"
API_BASE="${GATEWAY_URL}/api/v1"
Expand Down
Empty file modified demos/moveit_pick_place/planning-benchmark.sh
100644 → 100755
Empty file.
4 changes: 2 additions & 2 deletions demos/moveit_pick_place/setup-triggers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Create fault-monitoring trigger for moveit pick-and-place demo
# Alerts on any fault change reported by the manipulation monitor
export ENTITY_TYPE="apps"
# Uses ROS node name (underscore) - must match reporting_sources in FaultEvent
export ENTITY_ID="manipulation_monitor"
# Gateway normalises entity IDs with hyphens (matches the registered apps id).
export ENTITY_ID="manipulation-monitor"
export INJECT_HINT="./inject-planning-failure.sh"
# shellcheck disable=SC1091
source "$(cd "$(dirname "$0")" && pwd)/../../lib/setup-trigger.sh"
2 changes: 1 addition & 1 deletion demos/moveit_pick_place/watch-triggers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
# Watch trigger events for moveit pick-and-place demo
# Connects to SSE stream and prints fault events in real time
export ENTITY_TYPE="apps"
export ENTITY_ID="manipulation_monitor"
export ENTITY_ID="manipulation-monitor"
# shellcheck disable=SC1091
source "$(cd "$(dirname "$0")" && pwd)/../../lib/watch-trigger.sh" "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Inject gripper jam - gripper controller stuck
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ros2 param set /actuation/gripper_controller inject_jam true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Reset all actuation node parameters to defaults
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ERRORS=0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Inject LiDAR sensor failure - high failure probability
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ros2 param set /perception/lidar_driver failure_probability 0.8

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Reset all perception node parameters to defaults
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ERRORS=0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Inject path planning delay - 5000ms processing time
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ros2 param set /planning/path_planner planning_delay_ms 5000

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
# Reset all planning node parameters to defaults
set -eu

# ROS setup.bash dereferences AMENT_TRACE_SETUP_FILES; relax nounset around it.
set +u
# shellcheck source=/dev/null
source /opt/ros/jazzy/setup.bash
# shellcheck source=/dev/null
source /root/demo_ws/install/setup.bash
set -u

ERRORS=0

Expand Down
Loading
Loading