From 27c34a556a6dbb889b57cb732e3bf97e10a7114d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 10 Jun 2026 08:14:25 -0400 Subject: [PATCH] docs(scripting): document {{equip}} equipment-relative tag paths --- CLAUDE.md | 2 +- docs/ScriptEditor.md | 77 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 172ed90e..f8da42f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,4 +170,4 @@ runtime publish gate uses, so what the editor accepts/rejects matches publish exactly. The backend lives in `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ScriptAnalysis/` (six minimal-API endpoints under `/api/script-analysis/*`, gated by the `FleetAdmin` policy). -See `docs/ScriptEditor.md` for the full guide. +See `docs/ScriptEditor.md` for the full guide. Scripts may use the `{{equip}}` token for equipment-relative tag paths that resolve per-equipment at deploy time — see the "Equipment-relative tag paths" section in `docs/ScriptEditor.md`. diff --git a/docs/ScriptEditor.md b/docs/ScriptEditor.md index 5da064fe..ac47b630 100644 --- a/docs/ScriptEditor.md +++ b/docs/ScriptEditor.md @@ -293,6 +293,83 @@ Register the replacement in `EndpointRouteBuilderExtensions.AddAdminUI` --- +## Equipment-relative tag paths (`{{equip}}`) + +### Why + +Today each VirtualTag bound to a script typically needs its own near-duplicate +script because tag paths are hard-coded absolutes (e.g. `TestMachine_001.Speed`). +The `{{equip}}` token breaks this coupling: point many VirtualTags' `ScriptId` at +a single template script, and each resolves the token to its own equipment's tag +base prefix at deploy time. No schema change is required — sharing a `Script` +record across VirtualTags already works; `{{equip}}` is what makes the shared +script resolve per-equipment. + +### Before / after + +**Before — one script per machine:** + +```csharp +// Script "Calc_TestMachine_001" — hard-coded, cannot reuse +return ctx.GetTag("TestMachine_001.Speed").Value; +``` + +**After — one shared template:** + +```csharp +// Script "Calc_Speed" — works for any machine +return ctx.GetTag("{{equip}}.Speed").Value; +``` + +`TestMachine_001` and `TestMachine_002` both bind `ScriptId = "Calc_Speed"`. +At deploy, each VirtualTag receives its own expanded copy: +`TestMachine_001.Speed` and `TestMachine_002.Speed` respectively. + +### Token rules + +- The token is `{{equip}}` (double braces, lowercase). +- It is substituted **only inside `ctx.GetTag(…)` / `ctx.SetVirtualTag(…)` first-argument + string literals** — comments, logger strings, and other code are untouched. +- The operator writes the separator and tail: `ctx.GetTag("{{equip}}.Speed")`, + `ctx.GetTag("{{equip}}.Sub.Field")`, etc. +- The token expands to the equipment's **tag base prefix** — the common + substring-before-the-first-dot of that equipment's configured driver-tag + `FullName` values. Example: tags `TestMachine_001.Speed` and + `TestMachine_001.Temp` → base `TestMachine_001`. + +### Validation requirement + +Saving a VirtualTag that is bound to a `{{equip}}`-using script is rejected in +the AdminUI if the equipment does not have at least one configured driver tag, or +if its tags span multiple object prefixes (i.e. the common prefix is ambiguous or +absent). The rejection message is surfaced as a clear validation error on the save +form. This check is enforced eagerly so that an unresolved `{{equip}}` token — +which would leave a path that resolves to nothing at runtime (Bad quality) — can +never reach the deployed artifact. + +### Editor support + +In the Monaco script editor (ScriptEdit page and the `/uns` virtual-tag modal's +inline script panel): + +- **Hover** — hovering a `{{equip}}` path literal shows an + *"Equipment-relative path — resolved at deploy"* note. +- **Completions** — typing `{{equip}}.` inside a `GetTag`/`SetVirtualTag` literal + offers completion of attribute leaf names (the part after the first dot of known + tag references in the catalog). + +### Maintainer note + +Substitution runs at the two compose seams — +`Phase7Composer.Compose` and `DeploymentArtifact.BuildEquipmentVirtualTagPlans` +— via the shared `ZB.MOM.WW.OtOpcUa.Commons.Types.EquipmentScriptPaths` helper, +**before** dependency extraction. The runtime, the static change-trigger +dependency graph, and the literal-only path rule are therefore all unchanged: +by the time they see the script, `{{equip}}` has been replaced with a concrete +tag-base prefix and the path is a normal string literal. + +--- + ## Known Limitations / Follow-ups - **Tag-path completion shows resolvable keys only.** Surfacing the UNS browse