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 onnode.uuid so two instances on the same workflow
run independent timers — typical when one zone tracks people and
another tracks vehicles.
Sustained
Timeout
Debounce
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 thetimed_condition node:
- Mode — pick
Sustained,Timeout, orDebounce. The other knobs grey out automatically when the selected mode ignores them (e.g.Min Durationis disabled inDebounce,Cooldownis disabled inTimeout). - Min Duration (s) — how long the signal must persist before the
gate fires (active for
Sustained, inactive forTimeout).0for “fire on first frame” semantics;5–30for 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. ForDebounce, 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_filteris 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”:- Edge perception workflow (camera_frame trigger):
camera_frame → call_model → anonymize → spatial_filter → timed_condition. The edge worker emits onecw.publish_alert(...)per gate-fired intrusion event. - 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.
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.