Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cyberwave.com/llms.txt

Use this file to discover all available pages before exploring further.

timed_condition is a stateful conditional node that holds the “active” signal for at least min_duration_s seconds before firing, then re-arms only after the input has been inactive for at least cooldown_s seconds. It is the generalised successor to the original presence_dwell node — the dwell semantics are still the default behaviour, but the input now accepts any signal shape (not just detection arrays), so the same primitive applies to non-vision workflows.
Backward compatibility. Workflows authored before the rename use the legacy presence_dwell subtype. The cloud executor and edge codegen continue to accept that string and resolve it to this same schema, so existing zone-based intrusion alerts keep working without edits. New nodes created from the palette use the canonical timed_condition subtype.

When to use this node

detection_event_gate (the existing class-change gate) emits on the first frame a class appears. That’s correct for “person detected at all” alerts but wrong for “person has been loitering in zone A for 10 seconds” — there is no time component in the event-gate state. timed_condition is the time-dimension counterpart:
NodeWhen it fires
detection_event_gate (on_enter)On the first frame a target class appears.
timed_condition (mode = "sustained")On the frame the cumulative “active” duration crosses the threshold.
timed_condition (mode = "timeout")On the frame the cumulative “inactive” duration crosses the threshold.
timed_condition (mode = "debounce")Immediately on the first activation, then suppressed for cooldown_s.

Modes

ModeBehaviour
sustainedFires once the signal has been continuously active for ≥ min_duration_s. Re-arms after cooldown_s of inactive signal.
timeoutWatchdog. Fires once the signal has been continuously inactive for ≥ min_duration_s. Re-arms automatically on the next active frame (cooldown is unused).
debounceLeading-edge fire with suppression. Fires immediately on the first activation, then suppresses re-fires for cooldown_s regardless of signal state. min_duration_s is unused.
All three modes share the same per-node state and the same input predicate (active-when-truthy / above-threshold / non-empty), so swapping modes never requires re-wiring the upstream graph. Unknown modes are rejected loudly by the cloud executor and the edge codegen so a stale UI can’t silently run the wrong shape.

Inputs

FieldTypeDescription
signal (legacy: detections)array | boolean | numberThe signal to evaluate. Detection array + non-empty (after the optional class filter) counts as active. Boolean is active when truthy. Number is active when ≥ threshold. The port is named detections for back-compat with workflows authored before the rename.
modestringOne of sustained (default), timeout, or debounce. See the Modes table above.
min_duration_snumberContinuous “active” seconds required before the gate fires (sustained) or continuous “inactive” seconds before the timeout fires (timeout). Ignored by debounce. 0 fires on first observation.
cooldown_snumberInactive seconds required after firing before the gate re-arms (sustained) or suppression window after firing (debounce). Ignored by timeout, which re-arms on the next active frame.
target_classesarrayOptional class allow-list applied when the signal is a detection array. Empty / null matches any class. Ignored for boolean and number signals.
thresholdnumberNumeric threshold used when the signal is a number. The signal is active when value ≥ threshold. Default 0. Ignored for boolean and array signals.

Outputs

FieldTypeDescription
condition_metbooleantrue on the frame the time threshold is first crossed; false otherwise.
dwell_msnumberHow long the signal has been continuously active when the gate fires; 0 when not firing. (Kept under the legacy dwell_ms name for back-compat with downstream wiring.)
matched_detectionsarrayDetections from the current frame that passed the target_classes filter when the signal is a detection array; empty list otherwise. Handy to forward into send_alert.metadata so an operator can see what triggered the gate.
first_seen_tsnumber | nullMonotonic seconds when the current active window started; null when the signal is currently inactive. Use it for relative “active for N seconds” math inside the same edge / runner process, not for cross-process correlation.

State machines

State is keyed on node.uuid so two instances on the same workflow run independent timers — typical when one zone tracks people and another tracks vehicles.

Sustained

   ┌─────────────────────┐    signal becomes active    ┌──────────────────────┐
   │   IDLE (re-armed)   │ ──────────────────────────▶│  ACTIVE (counting)   │
   └─────────────────────┘                              └──────────────────────┘
              ▲                                                    │
              │                                                    │  elapsed_ms ≥ min_duration_s
              │                                                    ▼
              │                  cooldown_s seconds        ┌──────────────────────┐
              └──────────────────── of inactivity ─────────│  COOLDOWN (fired)    │
                                                            └──────────────────────┘

Timeout

   ┌─────────────────────┐   signal goes inactive    ┌──────────────────────┐
   │   IDLE (re-armed)   │ ────────────────────────▶│  COUNTING (inactive) │
   └─────────────────────┘                            └──────────────────────┘
              ▲                                                    │
              │                                                    │  elapsed_ms ≥ min_duration_s
              │  next active frame                                 ▼
              └──────────────────────────────────────────  ┌──────────────────────┐
                                                            │  FIRED (one-shot)    │
                                                            └──────────────────────┘
The next active frame closes the inactive window and re-arms the gate immediately — there is no cooldown. Designed for “alert if the signal stays absent” patterns (occupancy timeout, missing heartbeat, stale telemetry).

Debounce

   ┌─────────────────────┐   signal becomes active    ┌──────────────────────┐
   │   IDLE (re-armed)   │ ─────────────────────────▶│  FIRED               │
   └─────────────────────┘                             └──────────────────────┘
              ▲                                                    │
              │                                                    │  cooldown_s elapsed
              │                                                    ▼
              │                                            ┌──────────────────────┐
              └────────────────────────────────────────────│  SUPPRESSING         │
                                                            └──────────────────────┘
Ignores min_duration_s and the signal value during the suppression window — re-fires only after cooldown_s has elapsed, on the next active frame.

Authoring

In the workflow editor inspector for the timed_condition node:
  • Mode — pick Sustained, Timeout, or Debounce. The other knobs grey out automatically when the selected mode ignores them (e.g. Min Duration is disabled in Debounce, Cooldown is disabled in Timeout).
  • Min Duration (s) — how long the signal must persist before the gate fires (active for Sustained, inactive for Timeout). 0 for “fire on first frame” semantics; 530 for loitering / intrusion alerts; higher for “left unattended” scenarios.
  • Cooldown (s) — for Sustained, how long the input must stay inactive after a fire before the gate re-arms. For Debounce, the suppression window after the leading-edge fire. Tune to your alert review cadence.
  • Target Classes — optional class allow-list (chips), only used when the upstream signal is a detection array. Leave empty when the upstream spatial_filter is already class-scoped.

Companion nodes

  • spatial_filter — the canonical upstream for detection-array signals: scope the gate to a polygon zone.
  • send_alert — the canonical downstream: fire an alert with the polygon and elapsed duration baked into the payload.

Edge implementation

When timed_condition (or its legacy presence_dwell alias) sits one hop downstream of spatial_filter (or directly downstream of call_model) on a camera_frame-triggered workflow, the codegen inlines the state machine into the generated wf_*.py worker as three module-level globals (_dwell_first_seen_<n>, _dwell_alerted_<n>, _dwell_last_clear_<n>) and a per-frame block reading either _detections (post-spatial-filter) or results.detections (no spatial filter). On the frame the active window first crosses min_duration_s, the implicit cw.publish_alert(...) loop fires exactly once over _dwell_matched. The gate then stays silent until the input has been empty for cooldown_s, at which point all three globals reset together so the next entry can fire again. State is in-process — restart the edge runtime and the timer resets to IDLE. The edge codegen handles the detection-array signal path for all three modes (sustained, timeout, debounce) and stays bit-for-bit identical to the cloud executor — the codegen parity tests guard the algorithm. Boolean / number signals still run through the cloud executor for now.

End-to-end alert pipeline

A common pattern is “polygon → timed condition → email”:
  1. Edge perception workflow (camera_frame trigger): camera_frame → call_model → anonymize → spatial_filter → timed_condition. The edge worker emits one cw.publish_alert(...) per gate-fired intrusion event.
  2. Cloud reactive workflow (alert trigger): alert_trigger(twin=…, alert_type=detection) → send_email. The cloud workflow runner wakes on every edge-emitted alert and sends the email — no per-frame cloud cost, only the dwell-gated one-shots.
The timed_condition cloud executor exists too and behaves identically, but it is intended for cloud-only graphs where detections (or boolean / number signals) are produced inside a single WorkflowUtils.execute() call (e.g. fed by a non-camera trigger). It is not designed for state to persist across separate alert-triggered cloud executions.