Skip to content

OPC-UA Plugin Integration #92

OPC-UA Plugin Integration

OPC-UA Plugin Integration #92

Workflow file for this run

name: OPC-UA Plugin Integration
permissions:
contents: read
# Runs the OpenPLC-based Docker integration suite for ros2_medkit_opcua.
# Triggers only when the plugin itself, the gateway plugin/provider API, or
# ros2_medkit_msgs change. A nightly cron provides a safety net against silent
# breaks that slip past the path filter.
on:
pull_request:
branches: [main]
paths:
- 'src/ros2_medkit_plugins/ros2_medkit_opcua/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/providers/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/error_codes.hpp'
- 'src/ros2_medkit_msgs/**'
- '.github/workflows/opcua-plugin.yml'
push:
branches: [main]
paths:
- 'src/ros2_medkit_plugins/ros2_medkit_opcua/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/providers/**'
- 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/error_codes.hpp'
- 'src/ros2_medkit_msgs/**'
- '.github/workflows/opcua-plugin.yml'
schedule:
# Daily at 04:00 UTC - safety net against breaks that slip past the path filter.
- cron: '0 4 * * *'
jobs:
unit-tests:
name: Unit tests (${{ matrix.ros_distro }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- ros_distro: humble
os_image: ubuntu:jammy
- ros_distro: jazzy
os_image: ubuntu:noble
- ros_distro: rolling
os_image: ubuntu:noble
continue-on-error: ${{ matrix.ros_distro == 'rolling' }}
container:
image: ${{ matrix.os_image }}
timeout-minutes: 60
defaults:
run:
shell: bash
steps:
- name: Install Git
run: |
apt-get update
apt-get install -y git
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up ROS 2 ${{ matrix.ros_distro }}
uses: ros-tooling/setup-ros@v0.7
with:
required-ros-distributions: ${{ matrix.ros_distro }}
- name: Install ccache
run: apt-get install -y ccache
- name: Cache ccache
uses: actions/cache@v4
with:
path: /root/.cache/ccache
key: ccache-opcua-${{ matrix.ros_distro }}-${{ github.sha }}
restore-keys: |
ccache-opcua-${{ matrix.ros_distro }}-
- name: Install dependencies
run: |
apt-get update
apt-get install -y ros-${{ matrix.ros_distro }}-test-msgs libyaml-cpp-dev libssl-dev
source /opt/ros/${{ matrix.ros_distro }}/setup.bash
rosdep update
# Only skip nav2_msgs because vda5050_agent declares it as a dep but
# the apt package is not available on all distros. We do NOT skip
# ament_cmake_clang_format or test_msgs here: upstream medkit
# packages (ros2_medkit_serialization, gateway) require them at
# configure time via find_package.
rosdep install --from-paths src --ignore-src -y \
--skip-keys='nav2_msgs'
- name: Build ros2_medkit_opcua (and upstream deps)
env:
CCACHE_DIR: /root/.cache/ccache
CCACHE_MAXSIZE: 500M
CCACHE_SLOPPINESS: pch_defines,time_macros
run: |
source /opt/ros/${{ matrix.ros_distro }}/setup.bash
# --packages-up-to naturally excludes unrelated packages
# (vda5050_agent, discovery plugins, etc.) because they are not in
# the dependency chain of ros2_medkit_opcua.
colcon build --symlink-install \
--packages-up-to ros2_medkit_opcua \
--cmake-args -DCMAKE_BUILD_TYPE=Release \
--event-handlers console_direct+
ccache -s
- name: Run unit tests
timeout-minutes: 15
run: |
source /opt/ros/${{ matrix.ros_distro }}/setup.bash
colcon test --return-code-on-test-failure \
--packages-select ros2_medkit_opcua \
--ctest-args -LE linter \
--event-handlers console_direct+
- name: Show test results
if: always()
run: colcon test-result --verbose
integration:
name: Integration (OpenPLC)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build OpenPLC container
run: |
docker build \
-t openplc-tank \
src/ros2_medkit_plugins/ros2_medkit_opcua/docker/openplc/
- name: Build gateway + OPC-UA plugin image
run: |
docker build \
-f src/ros2_medkit_plugins/ros2_medkit_opcua/docker/Dockerfile.gateway \
-t gateway-opcua .
- name: Start OpenPLC
timeout-minutes: 3
run: |
docker network create plc-demo
docker run -d --name openplc --network plc-demo openplc-tank
for i in $(seq 1 60); do
if docker logs openplc 2>&1 | grep -q "PLC State: RUNNING"; then
echo "OpenPLC running after $((i * 2))s"
break
fi
if [ "$i" -eq 60 ]; then
echo "FAIL: OpenPLC did not start"
docker logs openplc 2>&1 | tail -20
exit 1
fi
sleep 2
done
- name: Start gateway
run: |
docker run -d --name gateway --network plc-demo -p 8080:8080 \
-e ROS_DOMAIN_ID=60 \
-e OPCUA_ENDPOINT_URL="opc.tcp://openplc:4840/openplc/opcua" \
-e OPCUA_NODE_MAP_PATH="/config/tank_nodes.yaml" \
gateway-opcua \
bash -c "
mkdir -p /var/lib/ros2_medkit/rosbags /config
echo 'manifest_version: \"1.0\"' > /config/manifest.yaml
source /opt/ros/jazzy/setup.bash && source /root/ws/install/setup.bash
PLUGIN_PATH=\$(find /root/ws/install -name 'libros2_medkit_opcua_plugin.so' | head -1)
ros2 run ros2_medkit_gateway gateway_node \
--ros-args --params-file /config/gateway_params.yaml \
-p plugins.opcua.path:=\$PLUGIN_PATH \
-p discovery.mode:=hybrid \
-p discovery.manifest_path:=/config/manifest.yaml \
-p discovery.manifest_strict_validation:=false"
- name: Wait for PLC entities and live data
timeout-minutes: 3
run: |
echo "Waiting for entity discovery..."
for i in $(seq 1 60); do
if curl -sf http://localhost:8080/api/v1/apps 2>/dev/null | \
jq -e '.items | map(.id) | contains(["tank_process"])' >/dev/null 2>&1; then
echo "Entities discovered after $((i * 2))s"
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout waiting for entities"
docker logs gateway 2>&1 | tail -20
exit 1
fi
sleep 2
done
echo "Waiting for PLC data to flow..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8080/api/v1/components/openplc_runtime/x-plc-status 2>/dev/null | \
jq -e '.connected == true and .poll_count > 0' >/dev/null 2>&1; then
echo "PLC data flowing after extra $((i * 2))s"
exit 0
fi
sleep 2
done
echo "WARNING: PLC not connected yet, running tests anyway"
curl -sf http://localhost:8080/api/v1/components/openplc_runtime/x-plc-status 2>/dev/null | jq . || true
docker logs gateway 2>&1 | tail -10
- name: Run integration tests
run: bash src/ros2_medkit_plugins/ros2_medkit_opcua/docker/scripts/run_integration_tests.sh
- name: Dump gateway logs on failure
if: failure()
run: |
echo "=== OpenPLC logs ==="
docker logs openplc 2>&1 | tail -60 || true
echo "=== Gateway logs ==="
docker logs gateway 2>&1 | tail -60 || true
- name: Cleanup
if: always()
run: |
docker rm -f gateway openplc 2>/dev/null || true
docker network rm plc-demo 2>/dev/null || true
integration-alarms:
name: Integration (AlarmConditionType)
# Issue #386: tests the native OPC-UA AlarmCondition subscription bridge
# against the test_alarm_server fixture (open62541 with FULL ns0 + alarms
# ON). Independent of the OpenPLC threshold-mode integration above; runs
# in parallel.
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install jq + asyncua (smoke test prerequisite)
run: |
sudo apt-get update
sudo apt-get install -y jq python3-pip
pip3 install --break-system-packages asyncua
- name: Run alarm integration suite
run: bash src/ros2_medkit_plugins/ros2_medkit_opcua/docker/scripts/run_alarm_tests.sh
- name: Dump container logs on failure
if: failure()
run: |
for c in alarm-test-server alarm-test-gateway; do
echo "=== ${c} logs ==="
docker logs "${c}" 2>&1 | tail -80 || true
done