Files
lmxopcua/src/Core/ZB.MOM.WW.OtOpcUa.Core.Scripting/ScriptContext.cs
Joseph Doherty a25593a9c6 chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the
Rider Solution Explorer mirrors the module structure. Folders: Core, Server,
Drivers (with a nested Driver CLIs subfolder), Client, Tooling.

- Move every project folder on disk with git mv (history preserved as renames).
- Recompute relative paths in 57 .csproj files: cross-category ProjectReferences,
  the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external
  mxaccessgw refs in Driver.Galaxy and its test project.
- Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders.
- Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL,
  integration, install).

Build green (0 errors); unit tests pass. Docs left for a separate pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:55:28 -04:00

81 lines
4.0 KiB
C#

using Serilog;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// <summary>
/// The API user scripts see as the global <c>ctx</c>. Abstract — concrete subclasses
/// (e.g. <c>VirtualTagScriptContext</c>, <c>AlarmScriptContext</c>) plug in the
/// actual tag-backend + logger + virtual-tag writer for each evaluation. Phase 7 plan
/// decision #6: scripts can read any tag, write only to virtual tags, and have no
/// other .NET reach — no HttpClient, no File, no Process, no reflection.
/// </summary>
/// <remarks>
/// <para>
/// Every member on this type MUST be serializable in the narrow sense that
/// <see cref="DependencyExtractor"/> can recognize tag-access call sites from the
/// script AST. Method names used from scripts are locked — renaming
/// <see cref="GetTag"/> or <see cref="SetVirtualTag"/> is a breaking change for every
/// authored script and the dependency extractor must update in lockstep.
/// </para>
/// <para>
/// New helpers (<see cref="Now"/>, <see cref="Deadband"/>) are additive: adding a
/// method doesn't invalidate existing scripts. Do not remove or rename without a
/// plan-level decision + migration for authored scripts.
/// </para>
/// </remarks>
public abstract class ScriptContext
{
/// <summary>
/// Read a tag's current value + quality + source timestamp. Path syntax is
/// <c>Enterprise/Site/Area/Line/Equipment/TagName</c> (forward-slash delimited,
/// matching the Equipment-namespace browse tree). Returns a
/// <see cref="DataValueSnapshot"/> so scripts branch on quality without a second
/// call.
/// </summary>
/// <remarks>
/// <paramref name="path"/> MUST be a string literal in the script source — dynamic
/// paths (variables, concatenation, method-returned strings) are rejected at
/// publish by <see cref="DependencyExtractor"/>. This is intentional: the static
/// dependency set is required for the change-driven scheduler to subscribe to the
/// right upstream tags at load time.
/// </remarks>
public abstract DataValueSnapshot GetTag(string path);
/// <summary>
/// Write a value to a virtual tag. Operator scripts cannot write to driver-sourced
/// tags — the OPC UA dispatch in <c>DriverNodeManager</c> rejects that separately
/// per ADR-002 with <c>BadUserAccessDenied</c>. This method is the only write path
/// virtual tags have.
/// </summary>
/// <remarks>
/// Path rules identical to <see cref="GetTag"/> — literal only, dependency
/// extractor tracks the write targets so the engine knows what downstream
/// subscribers to notify.
/// </remarks>
public abstract void SetVirtualTag(string path, object? value);
/// <summary>
/// Current UTC timestamp. Prefer this over <see cref="DateTime.UtcNow"/> in
/// scripts so the harness can supply a deterministic clock for tests.
/// </summary>
public abstract DateTime Now { get; }
/// <summary>
/// Per-script Serilog logger. Output lands in the dedicated <c>scripts-*.log</c>
/// sink with structured property <c>ScriptName</c> = the script's configured name.
/// Use at error level to surface problems; main <c>opcua-*.log</c> receives a
/// companion WARN entry so operators see script errors in the primary log.
/// </summary>
public abstract ILogger Logger { get; }
/// <summary>
/// Deadband helper — returns <c>true</c> when <paramref name="current"/> differs
/// from <paramref name="previous"/> by more than <paramref name="tolerance"/>.
/// Useful for alarm predicates that shouldn't flicker on small noise. Pure
/// function; no side effects.
/// </summary>
public static bool Deadband(double current, double previous, double tolerance)
=> Math.Abs(current - previous) > tolerance;
}