Files
scadalink-design/docs/plans/2026-05-18-whiletrue-trigger-mode-design.md

6.2 KiB

WhileTrue Trigger Mode for Conditional & Expression Script Triggers — Design

Date: 2026-05-18 Status: Approved (brainstorming) — implementation to follow

Context

Template script triggers of type Conditional and Expression currently fire once when their condition first holds:

  • Expression is edge-triggered — the script runs once per false→true transition of the boolean expression.
  • Conditional runs the script on each change of the monitored attribute for which the {operator, threshold} comparison is true.

There is no way to keep a script running while a condition remains true. This design adds a second mode, WhileTrue, alongside the existing behavior (referred to as OnTrue).

Decisions taken during brainstorming

  • WhileTrue is a timer re-fire cadence. On the false→true edge the script fires immediately; while the condition stays true it re-fires on a periodic timer; on the true→false edge the timer stops. It re-fires even if no attribute updates arrive (e.g. a static condition).
  • The re-fire interval is the script's existing MinTimeBetweenRuns — no new configuration field. One value serves as both the WhileTrue cadence and the general per-script throttle.
  • OnTrue is unchanged. A trigger config with no mode field parses as OnTrue, so every existing deployed template behaves identically.
  • Alarms are out of scope — alarm triggers are already level-based ("active while true").

Design

1. Trigger model & storage

Conditional and Expression trigger TriggerConfiguration JSON gains an optional mode field:

Trigger JSON shape
Conditional { attributeName, operator, threshold, mode? }
Expression { expression, mode? }

mode is "OnTrue" (default; absent ⇒ OnTrue) or "WhileTrue". Entity types are unchanged — TemplateScript.TriggerConfiguration stays string?.

2. Runtime evaluation (ScriptActor)

The internal trigger-config records gain a Mode:

internal enum TriggerMode { OnTrue, WhileTrue }
internal record ConditionalTriggerConfig(
    string AttributeName, string Operator, double Threshold, TriggerMode Mode) : ScriptTriggerConfig;
internal record ExpressionTriggerConfig(string Expression, TriggerMode Mode) : ScriptTriggerConfig;

OnTrue — current behavior, unchanged:

  • Conditional: fire on each monitored-attribute change for which the comparison is true.
  • Expression: fire once per false→true transition.

WhileTrue — the actor tracks the condition's current truth state:

  • false→true edge: fire once immediately (via TrySpawnExecution, which still respects MinTimeBetweenRuns against any prior run), then start a periodic Akka timer with both initial delay and interval set to MinTimeBetweenRuns.
  • Each timer tick: spawn an execution directly — the timer interval is itself the cadence, so ticks bypass the MinTimeBetweenRuns skip-check (which could otherwise drop a tick to sub-millisecond timing jitter).
  • true→false edge: cancel the timer.
  • The state transitions naturally re-arm: a later true after a false starts a fresh cycle.

Truth-state sources:

  • Conditional depends only on the monitored attribute, so it is re-evaluated on each change of that attribute (unchanged gate).
  • Expression is re-evaluated on every attribute change (unchanged).

A throwing/non-bool expression is treated as false (existing behavior); for WhileTrue that cleanly stops the timer.

Edge case — WhileTrue with no MinTimeBetweenRuns: a periodic timer has no interval to use, so the trigger degrades to a single edge fire and logs a warning. Deployment-time validation of this combination is a deliberate non-goal of this change.

New surface in ScriptActor: a TriggerMode on the two config records, one WhileTrueTick internal message, and start/stop timer helpers (the actor already implements IWithTimers for Interval triggers). Timer key "whiletrue-trigger".

3. Editors & codec

  • ScriptTriggerConfigCodec: ScriptTriggerModel gains a Mode (ScriptTriggerMode enum, default OnTrue). Parse reads mode for the Conditional and Expression kinds; Serialize writes it for those kinds. Absent/unrecognized modeOnTrue.
  • ScriptTriggerEditor.razor: the Conditional and Expression panels gain a mode <select> — "When the condition becomes true — once" (OnTrue) vs "Repeatedly while true" (WhileTrue) — with a hint that WhileTrue re-fires at the script's Min time between runs interval. BuildHint() is updated to describe the selected mode.

4. Error handling

  • Expression throws / returns non-bool → treated as false (logged as a script error, as today); for WhileTrue this stops the re-fire timer.
  • Malformed trigger JSON → trigger parses as inert (existing behavior).
  • WhileTrue with no MinTimeBetweenRuns → single fire + warning (see above).

5. Testing & verification

  • Runtime (ScriptActorTests) — a script body that calls Instance.SetAttribute(...) is observable as a SetStaticAttributeCommand on the instance-actor TestProbe; with a short MinTimeBetweenRuns:
    • WhileTrue conditional & expression: fire on the false→true edge, re-fire on the timer, stop on true→false, re-arm on a later true.
    • OnTrue regression: conditional & expression fire exactly as before.
    • WhileTrue with no MinTimeBetweenRuns: a single fire, no repeats.
  • Codec (ScriptTriggerConfigCodecTests, new)mode round-trips for Conditional and Expression; absent modeOnTrue; WhileTrue serializes.

Affected files

  • src/ScadaLink.SiteRuntime/Actors/ScriptActor.cs
  • src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cs
  • src/ScadaLink.CentralUI/Components/Shared/ScriptTriggerEditor.razor
  • tests/ScadaLink.SiteRuntime.Tests/Actors/ScriptActorTests.cs
  • tests/ScadaLink.CentralUI.Tests/Shared/ScriptTriggerConfigCodecTests.cs (new)
  • Docs: docs/requirements/Component-SiteRuntime.md, docs/requirements/Component-TemplateEngine.md (note the new mode).