Skip to main content
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:

Modes

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

Outputs

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

Timeout

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

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

timed_condition requires an explicit send_alert node downstream to publish anything. The legacy implicit-alert path on call_model that used to inline a dwell state machine into the generated wf_*.py worker has been retired — alert emission now lives entirely in send_alert (see Raising alerts from detections). The dispatcher (EdgeWorkflowCompiler) routes any camera_frame workflow that contains a send_alert node through WorkflowCodeAssembler rather than the single-hop WorkerCodegen path, and the assembler emits the explicit alert calls. A timed_condition wired downstream of call_model without a trailing send_alert is reported as inert at compile time (WorkerCodegen.warnings carries the inert-timer text), so the editor can surface the dead-timer condition before the worker is deployed. The cloud workflow runner executes timed_condition via _execute_timed_condition_node in src/lib/workflow_utils.py, which is the canonical implementation of all three modes (sustained, timeout, debounce) and the source of the min_duration_s / cooldown_s defaults. State is keyed on node.uuid and lives in-process — restart the runtime and the timer resets to IDLE.

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.