Read-only export of DEV vd03 and QA vq03 (2026-11-12): the only
Children["DelmiaRecv"] user is CvdReactor.ProcessRecipeDownload (script 4,
identical on both envs; DEV 3 / QA 9 instances), and the only ScriptAlarm
.Severity reader is DEV TestMesMachine.AlarmStatus (script 42, SBTEST01/02).
QA has no #131 work.
Adds the original (rollback) and fixed bodies, a guarded check/apply/rollback
script that re-sends every script field, and the apply plan: #52 any time,
#131 with or after the dd4f1c2a binary. The old #131 body still compiles on
main but returns banded values; the fixed body is what fails to compile on the
deployed binaries. Rig evidence for both gates is in the README.
Refs #52 #131
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# Deploy prep: site-script fixes for Gitea #52 and #131
|
||||
|
||||
**Prepared:** 2026-11-12. **Not applied** to DEV or QA. Applying it is a deploy and needs the owner's approval.
|
||||
|
||||
**Where the facts come from:** a read-only export of DEV vd03 and QA vq03 taken at 2026-11-12 ~23:10 EST. The export used `template list|get --detail`, `shared-script list`, `api-method list|get`, `instance list` and `instance diff`, and nothing was written.
|
||||
|
||||
- **DEV** runs DisableLogin, so any credentials work.
|
||||
- **QA** has login enabled. It authenticates against the box-local GLAuth (`ScadaBridge-LDAP` service), so the export used the documented `multi-role` test user there. The username and password were passed through environment variables, never on the command line.
|
||||
|
||||
**Validated:** on the docker rig (main binary, which includes `dd4f1c2a`). See [Rig evidence](#rig-evidence).
|
||||
|
||||
## Files
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `original/CvdReactor.ProcessRecipeDownload.cs` | The live body of CvdReactor script 4. It is byte-identical on DEV and QA. **Rollback body.** |
|
||||
| `original/TestMesMachine.AlarmStatus.cs` | The live body of DEV TestMesMachine script 42. **Rollback body.** |
|
||||
| `original/*.meta.json` | Each script's other fields: trigger, parameters, return definition, lock and timeouts. |
|
||||
| `fixed/CvdReactor.ProcessRecipeDownload.cs` | Changes one line: `Children["DelmiaRecv"]` becomes `Children["DelmiaReceiver"]`. |
|
||||
| `fixed/TestMesMachine.AlarmStatus.cs` | Changes three `a.Severity` reads to `a.Priority`. It is byte-identical to `ALARM_CODE` in `docker/seed-mes-test-machine.sh` at `dd4f1c2a`. |
|
||||
| `apply.sh` | Takes `check`, `apply` or `rollback` and a gate number (`52` or `131`). Details below. |
|
||||
|
||||
### What `apply.sh` does
|
||||
|
||||
- **`check`** only reads.
|
||||
- **`apply` and `rollback`** refuse to touch a script unless its live body is byte-identical to the expected file.
|
||||
- **Metadata:** they re-send every metadata field. `template script update` *replaces* the parameter and return definitions, so a bare `--code` update would wipe them. They then read the fields back and compare them.
|
||||
- **Deploy:** they deploy the affected instances. `--no-deploy` skips this step.
|
||||
|
||||
## Inventory (live, 2026-11-12)
|
||||
|
||||
The export searched every template script and inbound API method on each env for `Children["…"]` names that aren't compositions, and for `.Severity` reads on a `ScriptAlarm`. There are no shared scripts on either env. Every instance's `instance diff` reports `isStale=false`, so the deployed scripts are the template bodies listed below.
|
||||
|
||||
| Env | Template / script | Problem | Instances to redeploy |
|
||||
|---|---|---|---|
|
||||
| DEV vd03 | `CvdReactor` (1) / `ProcessRecipeDownload` (4) | `Children["DelmiaRecv"]`. The only composition is `DelmiaReceiver`. **#52** | Z28061 (1), Z28062 (2), Z28061Sim (8) |
|
||||
| QA vq03/vq04 | `CvdReactor` (1) / `ProcessRecipeDownload` (4) | Same body, byte for byte. **#52** | Z28061 (1), Z28062 (2), Z28063 (9), Z28064 (11), Z28065 (12), Z28066 (13), Z28067 (10), Z28068 (14), Z28069 (15) |
|
||||
| DEV vd03 | `TestMesMachine` (30) / `AlarmStatus` (42) | Reads `a.Severity` in 3 places: the MinSeverity and MaxSeverity predicates and `Severity = a.Severity`. **#131** | SBTEST01 (9), SBTEST02 (10) |
|
||||
| QA | — | **No #131 work.** QA has no TestMesMachine. Its `CvdReactor.AlarmStatus` (39) is a stub that returns `Alarms = []`. No other script calls `Alarms.*`. | — |
|
||||
|
||||
Every other `Children[...]` reference on both envs resolves: TestMesMachine's `LeftMESReceiver`, `RightMESReceiver` and `DelmiaReceiver`.
|
||||
|
||||
The inbound methods `SimpleAlarmStatusRequest` and `AlarmStatusRequest` only *pass* `MinSeverity`/`MaxSeverity` to the site script as arguments. They never read a `ScriptAlarm`, so they need no change.
|
||||
|
||||
Two corrections to the issue texts:
|
||||
|
||||
- **#131: the old body still compiles on main.** `ScriptAlarm.Severity` still exists after `dd4f1c2a`; it now holds the banded value. So the deployed scripts keep compiling and running on the new binary, but they return **wrong numbers and inverted filters** (rig evidence, Q1–Q3). The compile problem runs the other way: the **fixed** body references `ScriptAlarm.Priority`, which the deployed binaries don't have. Neither DEV `424db155` nor QA `b3021e7c` contains `dd4f1c2a`. On the rig, a body with an unknown `ScriptAlarm` member saves fine, because save does no compile check. Deploying it is refused with `Pre-deployment validation failed: … ScriptCompilation`.
|
||||
- **#131: DEV's CvdReactor has no `AlarmStatus` script at all.** So `SimpleAlarmStatusRequest` for Z2806x answers "not supported". The `CvdReactor.SimpleAlarmStatus`/`AlarmStatus` pattern in `docs/plans/2026-06-30-mes-alarm-status-api.md` §5.3 is a doc pattern and is not deployed anywhere. If it is ever authored, write it with `a.Priority`.
|
||||
|
||||
## Order relative to the ScadaBridge binary deploy
|
||||
|
||||
### #52: any time, independent of the binary
|
||||
|
||||
The fixed body uses only APIs that the DEV and QA binaries already run, because the current body already uses them. The fix is one string literal.
|
||||
|
||||
- **Recommended:** apply it in the same window as the next binary deploy, so the instances redeploy only once.
|
||||
- **Z28061 on the wonder boxes is also used by another team.** Agree a slot before redeploying it; the redeploy restarts the instance actor. Z28062 is the safe one to test against.
|
||||
|
||||
### #131: DEV only, with or after the binary that contains `dd4f1c2a`, in the same window
|
||||
|
||||
1. Deploy the new ScadaBridge binary to central and site.
|
||||
2. Immediately run `apply.sh apply 131`, before MES alarm traffic is trusted again.
|
||||
|
||||
**Applied before the binary:** the template saves, but the SBTEST instance deploys are refused by pre-deployment compilation. The site keeps the old body, which is harmless. Run `apply.sh rollback 131 --no-deploy` to put the template back.
|
||||
|
||||
**Binary without this step:** MES alarm-status output for SBTEST01/02 changes. Values come back banded (800→250, 300→750, 700→500), and the MinSeverity and MaxSeverity filters select the wrong alarms.
|
||||
|
||||
**Alternative:** re-run `docker/seed-mes-test-machine.sh` against vd03. It produces the same AlarmStatus body. But it also rewrites the inbound methods `SimpleAlarmStatusRequest` and `AlarmStatusRequest` and every other TestMesMachine script, so diff those first. `apply.sh` changes only the one script.
|
||||
|
||||
## Apply (from the Mac, after approval)
|
||||
|
||||
```bash
|
||||
cd ~/Desktop/ScadaBridge && dotnet build src/ZB.MOM.WW.ScadaBridge.CLI # apply.sh uses bin/Debug/net10.0/scadabridge (override: SB_CLI)
|
||||
P=docs/deployment/deploy-prep/2026-11-12-gates-52-131
|
||||
|
||||
# DEV (DisableLogin: any credentials)
|
||||
export SCADABRIDGE_URL=http://wonder-app-vd03.zmr.zimmer.com:8085 SCADABRIDGE_USERNAME=x SCADABRIDGE_PASSWORD=x
|
||||
bash $P/apply.sh check 52; bash $P/apply.sh apply 52 # any time
|
||||
bash $P/apply.sh check 131 # after the dd4f1c2a+ binary is up on DEV…
|
||||
bash $P/apply.sh apply 131 # …in the same window
|
||||
|
||||
# QA (login enabled; box-local GLAuth; export the password from a prompt, not argv)
|
||||
export SCADABRIDGE_URL=http://wonder-app-vq03.zmr.zimmer.com:8085 SCADABRIDGE_USERNAME=multi-role
|
||||
read -rs SCADABRIDGE_PASSWORD && export SCADABRIDGE_PASSWORD
|
||||
bash $P/apply.sh check 52; bash $P/apply.sh apply 52 # 9 instances; vq03 is the active central
|
||||
```
|
||||
|
||||
**Manual equivalent:**
|
||||
|
||||
1. Update the script with `scadabridge template script update`, passing every flag:
|
||||
- `--id <script id> --name <name> --trigger-type <type> --code "$(cat fixed/<x>.cs)"`
|
||||
- `--parameters "<parameterDefinitions>"` and `--return-def "<returnDefinition>"`, both from `original/<x>.meta.json`
|
||||
2. Run `scadabridge instance deploy --id <n>` for each instance in the inventory.
|
||||
|
||||
## Verify
|
||||
|
||||
- **#52 (read-only first):** run `scadabridge debug snapshot --id 2` on Z28062. `DelmiaReceiver.ReadyFlag` is the value the fixed script now reads.
|
||||
- **#52 end-to-end:** a `DelmiaRecipeDownload` call drives the real Galaxy handshake on Z28062, so it needs the owner's go-ahead. Use either a live `WWNotifier` call or `scadabridge flow-debug run-method --method-id 4 --params '{"MachineCode":"Z28062",…}'`.
|
||||
- The answer must no longer be `Receiver ready flag not set to true` while the flag is true.
|
||||
- The DEV site log must stop showing `GetAttribute: attribute 'DelmiaRecv.ReadyFlag' not found`.
|
||||
- **#131:** on vd03, run `SCADABRIDGE_URL=http://wonder-app-vd03.zmr.zimmer.com:8085 bash docker/verify-mes-test-machine.sh` (the `RECIPE_ROOT`/`RECIPE_OUTSIDE` values are in its header). Groups C and D must pass: C3 `Severity == 800`, C7 MinSeverity 500, C9 MaxSeverity 750. These are the same values the scripts returned before #129.
|
||||
- Record the apply in `docs/deployment/deployment-records/`.
|
||||
|
||||
## Rollback
|
||||
|
||||
`bash $P/apply.sh rollback 52` and `bash $P/apply.sh rollback 131` write back `original/*.cs`, byte for byte, and redeploy. Rollback is guarded the same way: it refuses if the live body is not the fixed file.
|
||||
|
||||
Rolling #131 back while the new binary is still running brings back the banded values. Only roll it back together with the binary.
|
||||
|
||||
## Rig evidence
|
||||
|
||||
**Setup:** the docker rig on main, with `AlarmPriorityBands` in the deployed Commons. The rig was idle: no management calls in the previous 60 min.
|
||||
|
||||
### #52
|
||||
|
||||
A temporary rig template `CvdReactor` held the DEV `ProcessRecipeDownload` body and its parameter schema. It composed the rig's static `DelmiaReceiverSim` as `DelmiaReceiver`. The template and its instance GATE52RIG were deleted afterwards. Each run was driven by `flow-debug run-script`, and the `ReadyFlag` value was confirmed in `debug snapshot` first.
|
||||
|
||||
| Body | ReadyFlag | Result |
|
||||
|---|---|---|
|
||||
| original | true | `Receiver ready flag not set to true`; site WRN `attribute 'DelmiaRecv.ReadyFlag' not found on instance 'GATE52RIG'` |
|
||||
| original | false | `Receiver ready flag not set to true` |
|
||||
| fixed | true | Got past the flag check into the batch write: `Batch write failed: Attribute 'DelmiaReceiver.DownloadPath' is not a data-sourced attribute` (expected, see below) |
|
||||
| fixed | false | `Receiver ready flag not set to true` (negative control: the flag really is read) |
|
||||
|
||||
The rig receiver is static, and `WriteBatchAndWaitAsync` only accepts data-sourced attributes. On the wonder boxes, `DelmiaReceiver.*` is Galaxy-bound, so the batch write proceeds there. The handshake code after the flag check is unchanged by this fix.
|
||||
|
||||
`apply → rollback → apply` round-tripped. The revision hash returned to `sha256:b2058525…` on rollback and `sha256:1da04831…` on apply. A repeat apply was a no-op.
|
||||
|
||||
### #131
|
||||
|
||||
The rig's `TestMesMachine` (5305) `AlarmStatus` was byte-identical to DEV's script 42 when the test started. Alarms were tripped with `VacuumPressure=12`, `DoorOpen=true` and `SensorStatus=-1`, which raises HighVacuumFault (priority 800, MES), DoorOpenAlarm (300) and SensorFault (700; MES on SBTEST02). Values are shown as (name, Severity, IsFlaggedForMES).
|
||||
|
||||
| Query | Original body on main (wrong) | Fixed body on main |
|
||||
|---|---|---|
|
||||
| Q1 SBTEST01 all | Door 750, HighVac 250, Sensor 500 | Door 300, HighVac 800, Sensor 700 |
|
||||
| Q2 MinSeverity=500 | Door, Sensor | HighVac, Sensor |
|
||||
| Q3 SBTEST02 FlaggedOnly + MaxSeverity=750 | HighVac, Sensor | Sensor |
|
||||
| Q4 FlaggedOnly | HighVac 250 | HighVac 800 |
|
||||
|
||||
- **The fixed column matches the pre-#129 expectations** in `docker/verify-mes-test-machine.sh` (C3, C7, C9).
|
||||
- **Compile gate (positive control):** a body with an unknown `ScriptAlarm` member saved, and its deploy was refused with `Pre-deployment validation failed: … ScriptCompilation`. That is what the fixed body would hit on the current DEV binary.
|
||||
- **Guard:** with that unknown body live, `apply.sh apply 131` answered `REFUSED: live body is UNKNOWN`.
|
||||
- **Round trip:** `rollback → apply` round-tripped.
|
||||
- **Rig end state:** SBTEST01/02 are left on the fixed body (what `seed-mes-test-machine.sh` seeds since `dd4f1c2a`) with the alarm sources reset.
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Apply / roll back the Gitea #52 and #131 site-script fixes on one ScadaBridge central.
|
||||
# Prepared 2026-11-12; see README.md in this folder for the order relative to the binary deploy.
|
||||
#
|
||||
# SCADABRIDGE_URL=http://wonder-app-vd03.zmr.zimmer.com:8085 \
|
||||
# SCADABRIDGE_USERNAME=... SCADABRIDGE_PASSWORD=... \
|
||||
# bash apply.sh <check|apply|rollback> <52|131> [--no-deploy]
|
||||
#
|
||||
# check READ-ONLY. Prints, per target script, whether the live body is the ORIGINAL (deployed
|
||||
# 2026-11-12), the FIXED body, or something else, and whether each instance is stale.
|
||||
# apply Guarded update: refuses unless the live body is byte-identical to original/<script>.cs
|
||||
# (or already fixed -> no-op). Re-sends every other field from original/<script>.meta.json
|
||||
# (`template script update` REPLACES parameters/return-def/trigger-config — omitting them
|
||||
# would wipe them). Then deploys the affected instances unless --no-deploy.
|
||||
# rollback The reverse: requires the live body to be fixed/<script>.cs, writes original/<script>.cs.
|
||||
#
|
||||
# Credentials come from the environment only (SCADABRIDGE_USERNAME / SCADABRIDGE_PASSWORD); nothing
|
||||
# is passed on argv. DEV vd03 runs DisableLogin (any value works); QA needs a real login.
|
||||
#
|
||||
# #52 -> template CvdReactor, script ProcessRecipeDownload; deploys every CvdReactor instance.
|
||||
# #131 -> template TestMesMachine, script AlarmStatus; deploys every TestMesMachine instance
|
||||
# (SBTEST01/SBTEST02, DEV only — QA has no TestMesMachine and no script reading .Severity).
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO="$(cd "$HERE/../../../.." && pwd)"
|
||||
export SB_CLI="${SB_CLI:-$REPO/src/ZB.MOM.WW.ScadaBridge.CLI/bin/Debug/net10.0/scadabridge}"
|
||||
export SB_URL="${SCADABRIDGE_URL:?set SCADABRIDGE_URL}" SB_HERE="$HERE"
|
||||
export SB_MODE="${1:?check|apply|rollback}" SB_GATE="${2:?52|131}" SB_NODEPLOY="${3:-}"
|
||||
: "${SCADABRIDGE_USERNAME:?set SCADABRIDGE_USERNAME}" "${SCADABRIDGE_PASSWORD:?set SCADABRIDGE_PASSWORD}"
|
||||
|
||||
python3 - <<'PY'
|
||||
import json, os, subprocess, sys
|
||||
|
||||
CLI, URL, HERE = os.environ["SB_CLI"], os.environ["SB_URL"], os.environ["SB_HERE"]
|
||||
MODE, GATE, NODEPLOY = os.environ["SB_MODE"], os.environ["SB_GATE"], os.environ["SB_NODEPLOY"] == "--no-deploy"
|
||||
TARGET = {"52": ("CvdReactor", "ProcessRecipeDownload"), "131": ("TestMesMachine", "AlarmStatus")}[GATE]
|
||||
KEY = f"{TARGET[0]}.{TARGET[1]}"
|
||||
|
||||
def cli(*args, timeout=300):
|
||||
r = subprocess.run([CLI, *args, "--url", URL, "--format", "json"], capture_output=True, text=True, timeout=timeout)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit(f"FAILED {' '.join(args[:3])}: {(r.stderr or r.stdout).strip()[:600]}")
|
||||
return json.loads(r.stdout) if r.stdout.strip() else None
|
||||
|
||||
def body(kind): return open(os.path.join(HERE, kind, KEY + ".cs"), encoding="utf-8").read()
|
||||
ORIG, FIXED = body("original"), body("fixed")
|
||||
META = json.load(open(os.path.join(HERE, "original", KEY + ".meta.json"), encoding="utf-8"))
|
||||
|
||||
tmpl = next((t for t in cli("template", "list") if t["name"] == TARGET[0] and not t.get("parentTemplateId")), None)
|
||||
if tmpl is None:
|
||||
print(f"{URL}: no base template {TARGET[0]} — nothing to do for #{GATE}"); sys.exit(0)
|
||||
detail = cli("template", "get", "--id", str(tmpl["id"]), "--detail")
|
||||
script = next((s for s in detail["scripts"] if s["name"] == TARGET[1] and not s.get("isInherited")), None)
|
||||
if script is None:
|
||||
print(f"{URL}: template {TARGET[0]} has no own script {TARGET[1]}"); sys.exit(0)
|
||||
live = script.get("code") or ""
|
||||
state = "ORIGINAL" if live == ORIG else "FIXED" if live == FIXED else "UNKNOWN"
|
||||
insts = [i for i in cli("instance", "list") if i["templateId"] == tmpl["id"]]
|
||||
print(f"{URL}: {KEY} template={tmpl['id']} script={script['id']} live body = {state}")
|
||||
for i in insts:
|
||||
d = cli("instance", "diff", "--id", str(i["id"]))
|
||||
print(f" instance {i['uniqueName']} (id {i['id']}) stale={d.get('isStale')} deployedAt={d.get('deployedAt')}")
|
||||
if MODE == "check":
|
||||
sys.exit(0)
|
||||
|
||||
want, need = (FIXED, "ORIGINAL") if MODE == "apply" else (ORIG, "FIXED")
|
||||
if live == want:
|
||||
print(" already in the target state — no script update")
|
||||
if not any(cli("instance", "diff", "--id", str(i["id"])).get("isStale") for i in insts):
|
||||
print(" every instance current — nothing to deploy"); sys.exit(0)
|
||||
elif state != need:
|
||||
raise SystemExit(f" REFUSED: live body is {state}, expected {need}. Diff it against {HERE} before touching it.")
|
||||
else:
|
||||
args = ["template", "script", "update", "--id", str(script["id"]), "--name", script["name"],
|
||||
"--code", want, "--trigger-type", script["triggerType"]]
|
||||
for opt, field in (("--trigger-config", "triggerConfiguration"), ("--parameters", "parameterDefinitions"),
|
||||
("--return-def", "returnDefinition"), ("--min-time-between-runs", "minTimeBetweenRuns"),
|
||||
("--execution-timeout-seconds", "executionTimeoutSeconds")):
|
||||
v = META.get(field)
|
||||
if v not in (None, ""):
|
||||
args += [opt, str(v)]
|
||||
if META.get("isLocked"):
|
||||
args.append("--locked")
|
||||
cli(*args)
|
||||
after = next(s for s in cli("template", "get", "--id", str(tmpl["id"]), "--detail")["scripts"] if s["id"] == script["id"])
|
||||
for field in ("parameterDefinitions", "returnDefinition", "triggerConfiguration"):
|
||||
if (after.get(field) or None) != (META.get(field) or None):
|
||||
raise SystemExit(f" {field} changed by the update — restore it from original/{KEY}.meta.json")
|
||||
if (after.get("code") or "") != want:
|
||||
raise SystemExit(" body read back does not match the target file")
|
||||
print(f" script {script['id']} updated ({MODE}); metadata read back unchanged")
|
||||
|
||||
if NODEPLOY:
|
||||
print(" --no-deploy: instances left stale; deploy them yourself"); sys.exit(0)
|
||||
for i in insts:
|
||||
r = cli("instance", "deploy", "--id", str(i["id"]))
|
||||
print(f" deploy {i['uniqueName']}: {json.dumps(r)[:200]}")
|
||||
PY
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Generic DELMIA recipe-download — performed DIRECTLY on the composed DelmiaReceiver child
|
||||
// (mirrors how MesMoveIn writes the MESReceiver child) instead of delegating via CallScript.
|
||||
// No side logic; the receiver's own DelmiaReceiver.Download method holds the same handshake.
|
||||
var recv = Children["DelmiaReceiver"];
|
||||
System.Func<object,bool> IsTrue = v => { var s=v?.ToString(); return (v is bool b && b)||s=="1"||string.Equals(s,"true",System.StringComparison.OrdinalIgnoreCase); };
|
||||
System.Func<string,object?> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
|
||||
if (!IsTrue(recv.Attributes["ReadyFlag"]))
|
||||
return new { Result=false, ResultText="Receiver ready flag not set to true" };
|
||||
try {
|
||||
recv.Attributes["RecipeProcessedFlag"] = false;
|
||||
var data = new System.Collections.Generic.Dictionary<string, object?> {
|
||||
{ "DownloadPath", P("DownloadPath") },
|
||||
{ "WorkOrderNumber", P("WorkOrderNumber") },
|
||||
{ "PartNumber", P("PartNumber") },
|
||||
{ "JobStepNumber", P("JobStepNumber") },
|
||||
{ "Username", P("Username") },
|
||||
};
|
||||
if (!await recv.Attributes.WriteBatchAndWaitAsync(data, "RecipeDownloadFlag", true, "RecipeProcessedFlag", true, System.TimeSpan.FromSeconds(25)))
|
||||
return new { Result=false, ResultText="Timeout waiting for recipe to be processed" };
|
||||
return new {
|
||||
Result = IsTrue(recv.Attributes["RecipeProcessResult"]),
|
||||
ResultText = recv.Attributes["RecipeProcessResultText"]?.ToString() ?? ""
|
||||
};
|
||||
} catch (Exception ex) {
|
||||
return new { Result=false, ResultText="RecipeDownload failed: " + ex.Message };
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
var code = Parameters["MachineCode"]?.ToString() ?? "";
|
||||
var flaggedOnly = Parameters.Get<bool?>("FlaggedOnly") ?? false;
|
||||
var includeAck = Parameters.Get<bool?>("IncludeAcked") ?? true;
|
||||
var minSev = Parameters.Get<int?>("MinSeverity");
|
||||
var maxSev = Parameters.Get<int?>("MaxSeverity");
|
||||
var nameFilter = Parameters.ContainsKey("NameFilter") ? Parameters["NameFilter"]?.ToString() : null;
|
||||
try {
|
||||
var alarms = flaggedOnly ? await Alarms.MesReportableAsync() : await Alarms.CurrentAsync();
|
||||
var infos = alarms
|
||||
.Where(a => a.Active && !a.IsConfiguredPlaceholder)
|
||||
.Where(a => minSev == null || a.Priority >= minSev)
|
||||
.Where(a => maxSev == null || a.Priority <= maxSev)
|
||||
.Where(a => string.IsNullOrEmpty(nameFilter) || a.Name.Contains(nameFilter, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(a => includeAck || !a.Acknowledged)
|
||||
.Select(a => new {
|
||||
Name = a.Name,
|
||||
HierarchicalName = code + "." + a.Name,
|
||||
Description = !string.IsNullOrEmpty(a.Message) ? a.Message : !string.IsNullOrEmpty(a.AlarmTypeName) ? a.AlarmTypeName : a.Name,
|
||||
IsFlaggedForMES = a.IsMesReportable,
|
||||
Severity = a.Priority, // MES Severity = the raw 0-999 priority (ScriptAlarm.Priority, #129)
|
||||
StatusCode = a.Acknowledged ? "Triggered.Acked" : "Triggered",
|
||||
TriggeredDT = (a.OriginalRaiseTime ?? a.Timestamp).ToString("o"),
|
||||
AckDT = a.AckTime?.ToString("o"),
|
||||
AckComment = a.OperatorComment })
|
||||
.ToList();
|
||||
return new { WasSuccessful = true, ErrorText = (string)null, Alarms = infos };
|
||||
} catch (Exception ex) {
|
||||
return new { WasSuccessful = false, ErrorText = "AlarmStatus failed: " + ex.Message, Alarms = Array.Empty<object>() };
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Generic DELMIA recipe-download — performed DIRECTLY on the composed DelmiaReceiver child
|
||||
// (mirrors how MesMoveIn writes the MESReceiver child) instead of delegating via CallScript.
|
||||
// No side logic; the receiver's own DelmiaReceiver.Download method holds the same handshake.
|
||||
var recv = Children["DelmiaRecv"];
|
||||
System.Func<object,bool> IsTrue = v => { var s=v?.ToString(); return (v is bool b && b)||s=="1"||string.Equals(s,"true",System.StringComparison.OrdinalIgnoreCase); };
|
||||
System.Func<string,object?> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
|
||||
if (!IsTrue(recv.Attributes["ReadyFlag"]))
|
||||
return new { Result=false, ResultText="Receiver ready flag not set to true" };
|
||||
try {
|
||||
recv.Attributes["RecipeProcessedFlag"] = false;
|
||||
var data = new System.Collections.Generic.Dictionary<string, object?> {
|
||||
{ "DownloadPath", P("DownloadPath") },
|
||||
{ "WorkOrderNumber", P("WorkOrderNumber") },
|
||||
{ "PartNumber", P("PartNumber") },
|
||||
{ "JobStepNumber", P("JobStepNumber") },
|
||||
{ "Username", P("Username") },
|
||||
};
|
||||
if (!await recv.Attributes.WriteBatchAndWaitAsync(data, "RecipeDownloadFlag", true, "RecipeProcessedFlag", true, System.TimeSpan.FromSeconds(25)))
|
||||
return new { Result=false, ResultText="Timeout waiting for recipe to be processed" };
|
||||
return new {
|
||||
Result = IsTrue(recv.Attributes["RecipeProcessResult"]),
|
||||
ResultText = recv.Attributes["RecipeProcessResultText"]?.ToString() ?? ""
|
||||
};
|
||||
} catch (Exception ex) {
|
||||
return new { Result=false, ResultText="RecipeDownload failed: " + ex.Message };
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": 4,
|
||||
"templateId": 1,
|
||||
"name": "ProcessRecipeDownload",
|
||||
"triggerType": "call",
|
||||
"triggerConfiguration": null,
|
||||
"isLocked": false,
|
||||
"parameterDefinitions": "{\"type\":\"object\",\"properties\":{\"DownloadPath\":{\"type\":\"string\"},\"WorkOrderNumber\":{\"type\":\"string\"},\"PartNumber\":{\"type\":\"string\"},\"JobStepNumber\":{\"type\":\"string\"},\"Username\":{\"type\":\"string\"}},\"required\":[]}",
|
||||
"returnDefinition": null,
|
||||
"minTimeBetweenRuns": null,
|
||||
"executionTimeoutSeconds": null
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
var code = Parameters["MachineCode"]?.ToString() ?? "";
|
||||
var flaggedOnly = Parameters.Get<bool?>("FlaggedOnly") ?? false;
|
||||
var includeAck = Parameters.Get<bool?>("IncludeAcked") ?? true;
|
||||
var minSev = Parameters.Get<int?>("MinSeverity");
|
||||
var maxSev = Parameters.Get<int?>("MaxSeverity");
|
||||
var nameFilter = Parameters.ContainsKey("NameFilter") ? Parameters["NameFilter"]?.ToString() : null;
|
||||
try {
|
||||
var alarms = flaggedOnly ? await Alarms.MesReportableAsync() : await Alarms.CurrentAsync();
|
||||
var infos = alarms
|
||||
.Where(a => a.Active && !a.IsConfiguredPlaceholder)
|
||||
.Where(a => minSev == null || a.Severity >= minSev)
|
||||
.Where(a => maxSev == null || a.Severity <= maxSev)
|
||||
.Where(a => string.IsNullOrEmpty(nameFilter) || a.Name.Contains(nameFilter, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(a => includeAck || !a.Acknowledged)
|
||||
.Select(a => new {
|
||||
Name = a.Name,
|
||||
HierarchicalName = code + "." + a.Name,
|
||||
Description = !string.IsNullOrEmpty(a.Message) ? a.Message : !string.IsNullOrEmpty(a.AlarmTypeName) ? a.AlarmTypeName : a.Name,
|
||||
IsFlaggedForMES = a.IsMesReportable,
|
||||
Severity = a.Severity,
|
||||
StatusCode = a.Acknowledged ? "Triggered.Acked" : "Triggered",
|
||||
TriggeredDT = (a.OriginalRaiseTime ?? a.Timestamp).ToString("o"),
|
||||
AckDT = a.AckTime?.ToString("o"),
|
||||
AckComment = a.OperatorComment })
|
||||
.ToList();
|
||||
return new { WasSuccessful = true, ErrorText = (string)null, Alarms = infos };
|
||||
} catch (Exception ex) {
|
||||
return new { WasSuccessful = false, ErrorText = "AlarmStatus failed: " + ex.Message, Alarms = Array.Empty<object>() };
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": 42,
|
||||
"templateId": 30,
|
||||
"name": "AlarmStatus",
|
||||
"triggerType": "Call",
|
||||
"triggerConfiguration": null,
|
||||
"isLocked": false,
|
||||
"parameterDefinitions": "{\"type\":\"object\",\"properties\":{\"MachineCode\":{\"type\":\"string\"},\"FlaggedOnly\":{\"type\":\"boolean\"},\"IncludeAcked\":{\"type\":\"boolean\"},\"MinSeverity\":{\"type\":\"integer\"},\"MaxSeverity\":{\"type\":\"integer\"},\"NameFilter\":{\"type\":\"string\"}},\"required\":[\"MachineCode\"]}",
|
||||
"returnDefinition": "{\"type\":\"object\",\"properties\":{\"WasSuccessful\":{\"type\":\"boolean\"},\"ErrorText\":{\"type\":\"string\"},\"Alarms\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"Name\":{\"type\":\"string\"},\"HierarchicalName\":{\"type\":\"string\"},\"Description\":{\"type\":\"string\"},\"IsFlaggedForMES\":{\"type\":\"boolean\"},\"Severity\":{\"type\":\"integer\"},\"StatusCode\":{\"type\":\"string\"},\"TriggeredDT\":{\"type\":\"string\"},\"AckDT\":{\"type\":\"string\"},\"AckComment\":{\"type\":\"string\"}}}}},\"required\":[\"WasSuccessful\",\"Alarms\"]}",
|
||||
"minTimeBetweenRuns": null,
|
||||
"executionTimeoutSeconds": null
|
||||
}
|
||||
Reference in New Issue
Block a user