Skip to content

MonitorTag: add OffDelay (clear-side debounce) — symmetric off-delay partner to MinDuration to stop alarm chatter #353

Description

@HanSur94

Problem / motivation

MonitorTag already has a full staged alarm FSM — raw ConditionFn → optional hysteresis (AlarmOffConditionFn) → MinDuration on-delay debounce (applyDebounce_, libs/SensorThreshold/MonitorTag.m:708, which zeroes out ON-runs shorter than MinDuration). MinDuration filters short onset spikes so a brief excursion above threshold doesn't raise the alarm.

There is no complementary clear-side stage. The alarm de-asserts the instant the condition goes false — even for a single-sample dropout. So a signal hovering right at the threshold flaps the alarm off→on→off (alarm chatter). This is exactly the case that process-alarm practice (ISA-18.2 "delay-on-return") solves with an off-delay: MinDuration already covers the ON half; the OFF half is simply absent.

This is a direct "exists for onset, missing for clear" asymmetry, not a new subsystem.

Proposed feature

A new opt-in property and a symmetric debounce stage:

  • OffDelay = 0 — native parent-X units; 0 (default) = today's behavior.
  • The alarm clears only after the condition has been continuously false for ≥ OffDelay; OFF-runs shorter than OffDelay are bridged back to ON.
% Signal noisy right at 50 — currently the alarm chatters every sample;
% with OffDelay it stays asserted through brief dropouts and clears once
% the signal is genuinely back below threshold for >= 2 X-units.
m = MonitorTag('press_hi', st, @(x, y) y > 50, ...
               'MinDuration', 2, ...   % existing on-delay (ignore brief spikes)
               'OffDelay',    2);      % NEW clear-side debounce (ignore brief drops)

Rough sketch

  • Lib/class: libs/SensorThreshold/MonitorTag.m only.
  • Property: OffDelay = 0 in the public properties block (next to MinDuration, MonitorTag.m:112), with a set.OffDelay validator mirroring set.MinDuration (MonitorTag.m:476).
  • New private helper applyClearDebounce_ — a near-mirror of applyDebounce_ (MonitorTag.m:708) that fills OFF-runs shorter than OffDelay with ON, reusing findRuns_, with a carry-in start argument for the streaming path (symmetric to ongoingRunStart_).
  • Stage wiring: apply as a 4th stage after the MinDuration on-delay in both recompute_ (Stage 3, MonitorTag.m:405) and the appendData streaming path (MonitorTag.m:643). Add one carry-in field to cache_ (mirrors lastHystState_/ongoingRunStart_) so streaming stays deterministic/replayable.
  • Serialization: add s.offdelay = obj.OffDelay; to toStruct (mirrors s.minduration, MonitorTag.m:293) and 'OffDelay', MonitorTag.fieldOr_(s, 'offdelay', 0) to fromStruct (mirrors MonitorTag.m:1163).
  • Public API shape: name–value option 'OffDelay', <scalar >= 0> on the existing constructor; getXY() return contract unchanged (still a 0/1 vector on the parent grid).

Value

High for the target persona (a MATLAB sensor-analysis engineer watching a dashboard, not the live sample stream). A monitor bound to a StatusWidget/NumberWidget on a noisy channel currently chatters; OffDelay is the standard off-delay knob that stops it. It completes the two-sided debounce that every real alarm system carries, using machinery that already exists on the ON side.

Constraints check

  • Toolbox-free: ✅ run-length ops on a 0/1 vector — same primitives as applyDebounce_/findRuns_. No toolbox.
  • Backward-compatible: ✅ new property defaults to 0 → byte-identical output to today. Serialized tags that predate the field → fieldOr_(..., 0) → keep working. getXY()/Tag contract unchanged; existing scripts unaffected.
  • Pure MATLAB / Octave: ✅ no MEX, no new dependency; mirrors an existing pure-MATLAB helper.
  • Widget/Tag contract: ✅ works entirely through the existing MonitorTag/Tag interface.

Effort estimate

S. One class. One property + set validator, one private applyClearDebounce_ (near-mirror of the existing applyDebounce_), one cache_ carry-in field, two serialization lines, one class-based test (tests/suite/TestMonitorTag.m).


AI-proposed via /feature-scout — needs a human product decision before implementation.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions