feat(alarms): bridge driver comms-loss into scripted-alarm input quality (Layer 4 of #477) #481

Open
opened 2026-07-17 16:07:34 -04:00 by dohertj2 · 0 comments
Owner

Summary

Layer 4 of the condition-Quality chain (#477#478). #478 shipped scripted-alarm Quality derived from the worst of its input tags' qualities, but only for inputs whose driver publishes a data change carrying a Bad/Uncertain StatusCode (e.g. an OpcUaClient input forwarding a server's per-item Bad).

It does not cover a driver comms loss: a poll driver (Modbus/S7) whose device goes unreachable emits only DriverInstanceActor.ConnectivityChanged(false) and goes silent on the value feed — it does not republish its tags as Bad. So the scripted engine keeps the last-known Good value, WorstInputStatus stays Good, and:

  • the scripted condition's Quality stays Good (opposite of the "can I trust this condition's state?" intent), and
  • AreInputsReady stays true (last value is Good), so the predicate keeps evaluating a stale value after the source died.

Native alarms already solve this: DriverHostActor.OnDriverConnectivityChanged fans an AlarmQualityUpdate(Bad) to every native condition the driver owns (_alarmNodeIdByDriverRef). Scripted alarms need the symmetric bridge into their inputs.

The ask

Bridge driver connectivity into scripted-alarm input quality:

  • On ConnectivityChanged(false), mark every mux ref the driver publishes as Bad for the scripted engine (restored to real quality on reconnect, which self-heals via ResubscribeDesired republishing values).
  • The DriverHostActor currently tracks driver → native alarm nodes (_alarmNodeIdByDriverRef) but not driver → all tag refs; that per-driver ref set needs to exist to fan connectivity into the mux.
  • Resolve the null-value / cold-start asymmetry (#478 Finding 3): WorstInputStatus skips a null-value input as "no data yet" (contributes Good) to avoid a deploy-time Bad flash, but that makes a genuine runtime Bad with a null value indistinguishable from cold start. Distinguish "never pushed" (cold → Good) from "pushed Bad" (→ Bad), e.g. by tracking pushed-ness in DependencyMuxTagUpstreamSource.

Notes / considerations

  • Pushing a synthetic Bad into the mux also makes virtual tags computed from that input go Bad — arguably correct, but a behavior change to weigh (VT already has its own Bad-propagation path; confirm no double-handling).
  • Verify on the docker-dev rig with a Modbus/S7-fed scripted alarm: pause the fixture container (comms loss) → assert the scripted condition reports Bad on the wire; unpause → Good. (This is the same live-gate style the #477 native path used.)

References

  • #477 (Layers 1+2, native connectivity-driven quality) and its OnDriverConnectivityChanged bridge.
  • #478 (Layer 3, mux-delivered input quality) — this issue is its comms-loss extension. See docs/AlarmTracking.md §"Scripted alarms (Layer 3, #478)" "Coverage boundary" and docs/plans/2026-07-17-alarm-condition-quality-477-design.md.
## Summary **Layer 4** of the condition-Quality chain (#477 → #478). #478 shipped scripted-alarm `Quality` derived from the **worst of its input tags' qualities**, but only for inputs whose driver **publishes a data change carrying a Bad/Uncertain `StatusCode`** (e.g. an OpcUaClient input forwarding a server's per-item Bad). It does **not** cover a driver **comms loss**: a poll driver (Modbus/S7) whose device goes unreachable emits only `DriverInstanceActor.ConnectivityChanged(false)` and goes *silent* on the value feed — it does not republish its tags as Bad. So the scripted engine keeps the last-known **Good** value, `WorstInputStatus` stays Good, and: - the scripted condition's `Quality` stays **Good** (opposite of the "can I trust this condition's state?" intent), and - `AreInputsReady` stays true (last value is Good), so the predicate **keeps evaluating a stale value** after the source died. Native alarms already solve this: `DriverHostActor.OnDriverConnectivityChanged` fans an `AlarmQualityUpdate(Bad)` to every **native** condition the driver owns (`_alarmNodeIdByDriverRef`). Scripted alarms need the symmetric bridge into their **inputs**. ## The ask Bridge driver connectivity into scripted-alarm input quality: - On `ConnectivityChanged(false)`, mark every mux ref the driver publishes as `Bad` for the scripted engine (restored to real quality on reconnect, which self-heals via `ResubscribeDesired` republishing values). - The `DriverHostActor` currently tracks driver → **native alarm nodes** (`_alarmNodeIdByDriverRef`) but not driver → **all tag refs**; that per-driver ref set needs to exist to fan connectivity into the mux. - Resolve the **null-value / cold-start asymmetry** (#478 Finding 3): `WorstInputStatus` skips a null-value input as "no data yet" (contributes Good) to avoid a deploy-time Bad flash, but that makes a genuine runtime `Bad` with a null value indistinguishable from cold start. Distinguish "never pushed" (cold → Good) from "pushed Bad" (→ Bad), e.g. by tracking pushed-ness in `DependencyMuxTagUpstreamSource`. ## Notes / considerations - Pushing a synthetic Bad into the mux also makes **virtual tags** computed from that input go `Bad` — arguably correct, but a behavior change to weigh (VT already has its own Bad-propagation path; confirm no double-handling). - Verify on the docker-dev rig with a Modbus/S7-fed scripted alarm: pause the fixture container (comms loss) → assert the scripted condition reports `Bad` on the wire; unpause → `Good`. (This is the same live-gate style the #477 native path used.) ## References - #477 (Layers 1+2, native connectivity-driven quality) and its `OnDriverConnectivityChanged` bridge. - #478 (Layer 3, mux-delivered input quality) — this issue is its comms-loss extension. See `docs/AlarmTracking.md` §"Scripted alarms (Layer 3, #478)" "Coverage boundary" and `docs/plans/2026-07-17-alarm-condition-quality-477-design.md`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#481