Skip to content

CI: stop Draft runner flood, scope main and firmware pushes, package on demand #750

CI: stop Draft runner flood, scope main and firmware pushes, package on demand

CI: stop Draft runner flood, scope main and firmware pushes, package on demand #750

name: SMV Injector GUI
on:
push:
branches: [main]
paths:
- 'apps/smv_injector_gui/**'
- 'tools/smv_profile_inspect.cpp'
- 'include/ariec61850/sampled_values/publisher_profile.hpp'
- 'include/ariec61850/sampled_values/profile_family.hpp'
- 'include/ariec61850/sampled_values/iec61869_9_profile.hpp'
- 'include/ariec61850/scl/**'
- 'src/scl/**'
- 'tests/fixtures/scl/**'
- '.github/workflows/smv-injector-gui.yml'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'apps/smv_injector_gui/**'
- 'tools/smv_profile_inspect.cpp'
- 'include/ariec61850/sampled_values/publisher_profile.hpp'
- 'include/ariec61850/sampled_values/profile_family.hpp'
- 'include/ariec61850/sampled_values/iec61869_9_profile.hpp'
- 'include/ariec61850/scl/**'
- 'src/scl/**'
- 'tests/fixtures/scl/**'
- '.github/workflows/smv-injector-gui.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
profile-bridge-smoke:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
- name: JavaScript syntax
run: |
node --check apps/smv_injector_gui/app.js
node --check apps/smv_injector_gui/scl_bridge_bootstrap.js
node --check apps/smv_injector_gui/profile_bridge.js
- name: Required UI files
shell: bash
run: |
test -s apps/smv_injector_gui/index.html
test -s apps/smv_injector_gui/styles.css
test -s apps/smv_injector_gui/profile_bridge.css
test -s apps/smv_injector_gui/app.js
test -s apps/smv_injector_gui/scl_bridge_bootstrap.js
test -s apps/smv_injector_gui/profile_bridge.js
test -s apps/smv_injector_gui/host_server.py
test -s apps/smv_injector_gui/run.cmd
grep -q 'navigator.serial' apps/smv_injector_gui/app.js
grep -q '/api/scl/inspect' apps/smv_injector_gui/profile_bridge.js
- name: IEC 61869-9 authority boundary
shell: bash
run: |
core=include/ariec61850/sampled_values/iec61869_9_profile.hpp
publisher=include/ariec61850/sampled_values/publisher_profile.hpp
device=include/ariec61850/sampled_values/esp32p4_profile_support.hpp
bridge=apps/smv_injector_gui/profile_bridge.js
studio=apps/arstack_studio/qml/ProfileInspector.qml
grep -q 'iec61869_9_current_scale' "$core"
grep -q 'iec61869_9_voltage_scale' "$core"
grep -q 'resolve_iec61869_9_profile' "$publisher"
grep -q 'profile.profile_family == SvProfileFamily::iec61869_9' "$device"
grep -q 'compiledIec61869CountsPerUnit' "$bridge"
grep -q 'compiled standards profile and is read-only' "$bridge"
grep -q 'iec61869CurrentScale' "$studio"
grep -q 'iec61869VoltageScale' "$studio"
if grep -Eqi 'ABB|Siemens|Hitachi|OMICRON' "$core" "$publisher"; then
echo 'Vendor-specific IEC 61869-9 protocol branch is forbidden' >&2
exit 1
fi
if grep -Eq '0\.001|0\.01|1 mA/count|10 mV/count' "$studio"; then
echo 'Studio QML must consume compiled scale evidence, not own IEC 61869-9 constants' >&2
exit 1
fi
- name: Build smart SCL profile engine
shell: bash
run: |
cmake -S apps/smv_injector_gui/host -B build-smv-gui -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-smv-gui --target ariec61850_smv_profile_inspect
- name: Profile compiler regression
shell: bash
run: |
fixture=tests/fixtures/scl/sv-4800-structured-4i4v.scd
build-smv-gui/ariec61850_smv_profile_inspect "$fixture" > /tmp/profile-b.json
grep -q '"compatibilityClass":"B"' /tmp/profile-b.json
grep -q '"deviceSupport":"needs-profile-family-confirmation"' /tmp/profile-b.json
grep -q '"publisherRateHz":4800' /tmp/profile-b.json
build-smv-gui/ariec61850_smv_profile_inspect "$fixture" --profile-family iec61850-9-2 --counter-modulus 4800 > /tmp/profile-a.json
grep -q '"compatibilityClass":"A"' /tmp/profile-a.json
grep -q '"profileFamily":"iec61850-9-2"' /tmp/profile-a.json
grep -q '"deviceSupport":"ready"' /tmp/profile-a.json
build-smv-gui/ariec61850_smv_profile_inspect "$fixture" --profile-family iec61869-9 --counter-modulus 4800 > /tmp/profile-61869-invalid.json
grep -q '"compatibilityClass":"C"' /tmp/profile-61869-invalid.json
grep -q '"profile":null' /tmp/profile-61869-invalid.json
grep -q 'TCTR.AmpSv.instMag.i or TVTR.VolSv.instMag.i' /tmp/profile-61869-invalid.json
fixture_61869=tests/fixtures/scl/sv-61869-f4800s2-i4u4.scd
build-smv-gui/ariec61850_smv_profile_inspect "$fixture_61869" --profile-family iec61869-9 --counter-modulus 4800 > /tmp/profile-61869.json
grep -q '"schemaVersion":4' /tmp/profile-61869.json
grep -q '"compatibilityClass":"A"' /tmp/profile-61869.json
grep -q '"profileFamilyResolution":"resolved"' /tmp/profile-61869.json
grep -q '"deviceSupport":"unsupported-profile-family"' /tmp/profile-61869.json
grep -q '"code":"F4800S2I4U4"' /tmp/profile-61869.json
grep -q '"class":"preferred"' /tmp/profile-61869.json
grep -q '"scaleNumerator":1,"scaleDenominator":1000,"scaleUnit":"A"' /tmp/profile-61869.json
grep -q '"scaleNumerator":1,"scaleDenominator":100,"scaleUnit":"V"' /tmp/profile-61869.json
multi=tests/fixtures/scl/compact-multi-stream.scd
build-smv-gui/ariec61850_smv_profile_inspect "$multi" --stream-profile-family 0:iec61850-9-2 --counter-modulus 4000 > /tmp/profile-multi.json
python3 - <<'PY'
import json
data = json.load(open('/tmp/profile-multi.json', encoding='utf-8'))
assert len(data['streams']) >= 2
assert data['streams'][0]['profile']['profileFamily'] == 'iec61850-9-2'
for stream in data['streams'][1:]:
assert stream['profile']['profileFamily'] == 'unspecified'
assert stream['compatibilityClass'] == 'B'
PY
- name: Local backend smoke
shell: bash
run: |
python3 apps/smv_injector_gui/host_server.py --profile-tool build-smv-gui/ariec61850_smv_profile_inspect --port 8765 >/tmp/smv-gui-http.log 2>&1 &
server_pid=$!
trap 'kill ${server_pid} 2>/dev/null || true' EXIT
for attempt in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:8765/ >/tmp/smv-gui-index.html; then break; fi
sleep 0.2
done
grep -q 'ARStack61850' /tmp/smv-gui-index.html
curl --fail --silent -X POST \
-H 'X-File-Name: fixture.scd' \
--data-binary @tests/fixtures/scl/sv-4800-structured-4i4v.scd \
'http://127.0.0.1:8765/api/scl/inspect?counterModulus=4800&profileFamilies=0:iec61850-9-2' >/tmp/api.json
grep -q '"deviceSupport":"ready"' /tmp/api.json
windows-launcher-smoke:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Simulate locally deleted host scaffold
shell: powershell
run: Remove-Item 'apps\smv_injector_gui\host' -Recurse -Force
- name: Verify native-host launcher and self-heal
shell: cmd
run: apps\smv_injector_gui\run.cmd --build-only
- name: Verify Windows profile compiler
shell: powershell
run: |
$tool = Join-Path $PWD 'build-smv-gui\ariec61850_smv_profile_inspect.exe'
if (-not (Test-Path $tool)) { throw "profile compiler missing: $tool" }
& $tool 'tests\fixtures\scl\sv-4800-structured-4i4v.scd' '--profile-family' 'iec61850-9-2' '--counter-modulus' '4800' | Tee-Object -FilePath profile-a.json
if (-not (Select-String -Path profile-a.json -SimpleMatch '"deviceSupport":"ready"')) {
throw 'Windows profile compiler did not produce a deployable Class-A fixture result'
}