Files
ScadaBridge/docker/seed-handshake-demo.sh
T

86 lines
5.5 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# Seed a DATA-SOURCED flag handshake on site-a to prove the reset-aware
# Attributes.WriteBatchAndWaitAsync overload (Gitea #36) against a real OPC UA
# subscription — the reset echo must round-trip through the device before the
# trigger is written, and a SECOND run must not fast-path on the first run's
# cached Complete=true.
#
# What it creates (site-a / site id 1, connection id 1 "OPC PLC Simulator"):
# - Template HandshakeDemo
# * Trigger (Boolean) <- ns=3;s=Motor.Running writable, NOT simulated (holds what is written)
# * Complete (Boolean) <- ns=3;s=Pump.Running writable, NOT simulated
# * Payload (Double) <- ns=3;s=Motor.Speed writable, NOT simulated
# * Handshake (Call) — the reset-aware overload; returns the HandshakeResult fields
# * Device (ValueChange on Trigger) — plays the PLC: on Trigger==true sets Complete=true
# and clears Trigger (so the next run's trigger is a fresh edge)
# - Instance handshake-demo-1, bound + deployed
#
# Not idempotent (names must be unique). To re-seed:
# scadabridge instance delete --id <id> && scadabridge template delete --id <id>
# Prerequisites: infra (scadabridge-opcua) up, docker/deploy.sh, docker/seed-sites.sh.
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CLI="$PROJECT_ROOT/src/ZB.MOM.WW.ScadaBridge.CLI/bin/Debug/net10.0/scadabridge"
A=(--url "${SCADABRIDGE_URL:-http://localhost:9000}" --format=json --username "${SCADABRIDGE_USERNAME:-multi-role}" --password "${SCADABRIDGE_PASSWORD:-password}")
SITE_ID="${SITE_ID:-1}"
CONNECTION_ID="${CONNECTION_ID:-1}"
INSTANCE_NAME="${INSTANCE_NAME:-handshake-demo-1}"
json_id() { python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])'; }
echo "1/5 Creating template HandshakeDemo..."
TEMPLATE_ID=$("$CLI" template create "${A[@]}" --name HandshakeDemo \
--description "Gitea #36 live gate: reset-aware WriteBatchAndWaitAsync over real OPC UA flags" | json_id)
echo " id=$TEMPLATE_ID"
echo "2/5 Adding data-sourced attributes..."
"$CLI" template attribute add "${A[@]}" --template-id "$TEMPLATE_ID" --name Trigger --data-type Boolean --data-source "ns=3;s=Motor.Running" --description "Handshake trigger (writable, not simulated)" > /dev/null
"$CLI" template attribute add "${A[@]}" --template-id "$TEMPLATE_ID" --name Complete --data-type Boolean --data-source "ns=3;s=Pump.Running" --description "Handshake completion flag (writable, not simulated)" > /dev/null
"$CLI" template attribute add "${A[@]}" --template-id "$TEMPLATE_ID" --name Payload --data-type Double --data-source "ns=3;s=Motor.Speed" --description "Batch payload" > /dev/null
echo "3/5 Adding the Handshake (Call) script — reset-aware overload..."
read -r -d '' HANDSHAKE_CODE <<'CS' || true
// Gitea #36: reset Complete=false, WAIT for the device to echo it, then batch+trigger, then wait
// for Complete=true. Run this twice: the second run is the one the plain overload got wrong.
var values = new Dictionary<string, object?> { ["Payload"] = 42.5 };
var hs = await Attributes.WriteBatchAndWaitAsync(
values, "Trigger", true, "Complete", true, System.TimeSpan.FromSeconds(20),
"Complete", false, System.TimeSpan.FromSeconds(10));
Log.Info($"handshake matched={hs.Matched} phase={hs.Phase} timedOut={hs.TimedOut} resetFailed={hs.ResetFailed}");
return new { Matched = hs.Matched, Phase = hs.Phase.ToString(), TimedOut = hs.TimedOut, ResetFailed = hs.ResetFailed, Error = hs.ErrorMessage ?? "" };
CS
"$CLI" template script add "${A[@]}" --template-id "$TEMPLATE_ID" --name Handshake --trigger-type Call \
--return-def '{"type":"object","properties":{"Matched":{"type":"boolean"},"Phase":{"type":"string"},"TimedOut":{"type":"boolean"},"ResetFailed":{"type":"boolean"},"Error":{"type":"string"}},"required":["Matched","Phase"]}' \
--code "$HANDSHAKE_CODE" > /dev/null
echo "4/5 Adding the Device (ValueChange on Trigger) script — plays the PLC..."
read -r -d '' DEVICE_CODE <<'CS' || true
// Plays the device: on the rising edge of Trigger, complete the request and clear the trigger
// so the NEXT run's Trigger=true is a fresh change on the subscription. Its own Trigger=false
// write re-triggers this script, which returns at the guard.
var t = await Attributes.GetAsync("Trigger");
var ts = t?.ToString();
if (!((t is bool b && b) || ts == "1" || string.Equals(ts, "true", System.StringComparison.OrdinalIgnoreCase))) return null;
await Attributes.SetAsync("Complete", true);
await Attributes.SetAsync("Trigger", false);
return null;
CS
"$CLI" template script add "${A[@]}" --template-id "$TEMPLATE_ID" --name Device --trigger-type ValueChange \
--trigger-config '{"attributeName":"Trigger"}' --code "$DEVICE_CODE" > /dev/null
echo "5/5 Creating, binding and deploying instance $INSTANCE_NAME..."
INSTANCE_ID=$("$CLI" instance create "${A[@]}" --name "$INSTANCE_NAME" --template-id "$TEMPLATE_ID" --site-id "$SITE_ID" | json_id)
"$CLI" instance set-bindings "${A[@]}" --id "$INSTANCE_ID" \
--bindings "[[\"Trigger\",$CONNECTION_ID],[\"Complete\",$CONNECTION_ID],[\"Payload\",$CONNECTION_ID]]" > /dev/null
"$CLI" instance deploy "${A[@]}" --id "$INSTANCE_ID" > /dev/null
echo " instance id=$INSTANCE_ID"
echo ""
echo "=== Done. Live gate (run it TWICE — the second run is the #36 case): ==="
echo " $CLI flow-debug run-script --instance-id $INSTANCE_ID --script Handshake --show-logs ${A[*]}"
echo "Expect on BOTH runs: Matched=true, Phase=Response; the trace shows Complete write ->"
echo "Complete wait (reset echo, >0 ms on the 2nd run) -> batch+Trigger write -> Complete wait."