Conditional and Expression script triggers gain an optional `mode` field in their TriggerConfiguration JSON: - OnTrue (default): unchanged edge/per-change firing. An absent mode field parses as OnTrue, so every existing trigger config behaves identically. - WhileTrue: fires on the false->true edge, then re-fires on a periodic timer while the condition holds; stops on the true->false edge. The re-fire cadence is the script's MinTimeBetweenRuns; with none configured the trigger degrades to a single edge fire and logs a warning. ScriptActor tracks condition truth state and manages a dedicated "whiletrue-trigger" timer. ScriptTriggerConfigCodec and ScriptTriggerEditor round-trip the mode and expose an OnTrue/WhileTrue selector for the two trigger kinds. Design: docs/plans/2026-05-18-whiletrue-trigger-mode-design.md Tests: 7 ScriptActor runtime tests (edge fire, timer re-fire, stop, re-arm, no-MinTimeBetweenRuns degrade, OnTrue regressions) + 14 codec / editor tests. SiteRuntime suite 206 green, CentralUI suite 295 green.
6.2 KiB
WhileTrue Trigger Mode for Conditional & Expression Script Triggers — Design
Date: 2026-05-18 Status: Implemented
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→truetransition 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→trueedge the script fires immediately; while the condition stays true it re-fires on a periodic timer; on thetrue→falseedge 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
modefield parses asOnTrue, 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→truetransition.
WhileTrue — the actor tracks the condition's current truth state:
false→trueedge: fire once immediately (viaTrySpawnExecution, which still respectsMinTimeBetweenRunsagainst any prior run), then start a periodic Akka timer with both initial delay and interval set toMinTimeBetweenRuns.- Each timer tick: spawn an execution directly — the timer interval is
itself the cadence, so ticks bypass the
MinTimeBetweenRunsskip-check (which could otherwise drop a tick to sub-millisecond timing jitter). true→falseedge: cancel the timer.- The state transitions naturally re-arm: a later
trueafter afalsestarts 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:ScriptTriggerModelgains aMode(ScriptTriggerModeenum, defaultOnTrue).Parsereadsmodefor the Conditional and Expression kinds;Serializewrites it for those kinds. Absent/unrecognizedmode⇒OnTrue.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 callsInstance.SetAttribute(...)is observable as aSetStaticAttributeCommandon the instance-actorTestProbe; with a shortMinTimeBetweenRuns:- WhileTrue conditional & expression: fire on the
false→trueedge, re-fire on the timer, stop ontrue→false, re-arm on a latertrue. - OnTrue regression: conditional & expression fire exactly as before.
- WhileTrue with no
MinTimeBetweenRuns: a single fire, no repeats.
- WhileTrue conditional & expression: fire on the
- Codec (
ScriptTriggerConfigCodecTests, new) —moderound-trips for Conditional and Expression; absentmode⇒OnTrue;WhileTrueserializes.
Affected files
src/ScadaLink.SiteRuntime/Actors/ScriptActor.cssrc/ScadaLink.CentralUI/Components/Shared/ScriptTriggerConfigCodec.cssrc/ScadaLink.CentralUI/Components/Shared/ScriptTriggerEditor.razortests/ScadaLink.SiteRuntime.Tests/Actors/ScriptActorTests.cstests/ScadaLink.CentralUI.Tests/Shared/ScriptTriggerConfigCodecTests.cs(new)- Docs:
docs/requirements/Component-SiteRuntime.md,docs/requirements/Component-TemplateEngine.md(note the new mode).