---
title: "Trigger Reference · RUAL Documentation"
description: "All 28 start-of-flow device blocks, and the three rules that decide how often they fire: transitions, threshold crossings and unknown-previous seeding."
canonical: https://docs.rual.nl/home-automation/triggers
language: en
---

# Trigger Reference

The 28 blocks that start a flow when a device reports something, what fires each one, and the three rules that decide how often: transitions fire on the change, thresholds fire on the crossing, and an unknown previous value seeds instead of firing.

A trigger block has a flow OUT pin and no flow IN pin, which is what makes it a start-of-flow block. It is not reached by the flow walk; the device event dispatcher seeds its out pins and starts the flow from there, the same way a schedule block works.

Every trigger on this page fires only for **adopted** devices. The single exception is `device discovered`, whose purpose is to notice devices that are not adopted yet.

### The Rules That Surprise People

#### Transition Triggers Fire on the Change, Not on Every Report

A motion sensor does not send one message when somebody walks in. It re-announces `occupancy: true` every thirty seconds or so while a person is in the room, and a power meter reports every ten seconds whether or not anything changed.

Every binary trigger requires the value to have *changed*. So `presence detected` in a hallway with somebody standing in it fires **once**, on entry. Without that rule a "turn on the lights" flow would re-run continuously and a "send me a notification" flow would be unusable.

The one deliberate exception is `button pressed`. A button publishes `single` and stays `single` until the next press, so a value-equality test would swallow every repeat. A momentary capability has no meaningful "unchanged", so every press fires.

#### Threshold Triggers Fire on the Crossing, Not on the Level

`temperature changed` with `above = 25` fires **once**, when the room passes 25 °C. It does not fire again at 25.5, or 26, or every minute for the rest of the afternoon. It fires again only after the reading has gone back below 25 and crossed up again.

This is the single most common way a threshold automation turns into a notification flood, so the rule is in the platform rather than in your flow. The exact test is: the new reading is above the threshold, and the previous one was at or below it.

One deliberate wrinkle. A **first-ever** reading above the threshold counts as a crossing, because there is no previous value to have crossed from, and staying silent until the second reading would mean a node restart swallows an alarm.

Setting neither `above` nor `below` turns the threshold off entirely and the trigger fires on every changed reading, which for a sensor reporting each minute is a busy flow. Setting both fires on either crossing. Note that 0 is a legitimate threshold and is treated as one: leaving a pin empty is what turns it off, not setting it to zero.

#### An Unknown Previous Value Seeds Instead of Firing

When the node has no previous value for a capability, an incoming reading is recorded **without** counting as a transition. Nothing fires; the value is simply learned.

That rule exists because of what a restart otherwise does. With an empty registry, every adopted sensor re-announces the state it has been in for hours, and treating each first report as a change turns a routine restart into a burst of "door opened" and "light turned on" for things that did neither. A window open all night would announce itself as just opened at 03:00, run the alarm flow and switch the lights on.

There are exactly three exceptions, where the asymmetry runs the other way: `smoke`, `gas` and `water_leak`. A duplicate "smoke detected" is an annoyance; a missed one is the thing the sensor exists to prevent. So a node that comes back up to an alarm already sounding says so, and accepts the occasional repeat. Occupancy, contact and vibration are deliberately not on that list: they are the high-frequency ones, they are what a restart most often finds mid-state, and nothing catastrophic follows from learning about them one report late.

Momentary capabilities are also unaffected, because they have no persistent state to seed. Every button press is an event by definition.

In practice you will rarely see this, because last-known state is restored from Redis before the adapters connect, so the first report after a restart usually *does* have something to compare against. The seeding rule is what makes the behaviour correct even when it does not: a fresh install, a flushed Redis, a device heard from for the first time. See [What Survives a Restart](https://docs.rual.nl/home-automation/devices#persistence).

### Every Trigger Has the Same Shape

Two in-pins narrow which devices a trigger listens to, and both are optional.

| In-pin | Effect |
| --- | --- |
| `device` | Only this device. Accepts the platform id, the native id or the exact device name. **Leave it empty and the trigger fires for every matching device**, which is what makes one block able to be the whole-house version. |
| `room` | Only devices in this room, compared case-insensitively. Empty means every room. |

Leaving the device pin optional is the design decision that keeps this usable. Requiring it would force one flow per sensor, which is the shape that makes home automation tedious.

Out-pins are identical on all of them, and the two access-entry triggers add three more:

| Out-pin | Type | Carries |
| --- | --- | --- |
| `flow` | flow | The chain to run. |
| `device` | object | The whole device: id, name, room, type, capabilities, current state. Branch on model or battery without a second block. |
| `device id` | value | The stable platform id, ready to feed another block's device pin. |
| `name` | value | The friendly name. There because the common case is putting it in a message, and reaching into an object for that is friction. |
| `room` | value | The device's room, empty if none. |
| `type` | value | `sensor`, `light`, `switch`, `climate`, `cover`, `lock`, `button`, `door` or `camera`. |
| `capability` | value | Which capability changed. The pin to branch on when using the catch-all trigger. |
| `value` | value | The new value. |
| `previous` | value | The value before this change. Empty on the first report. |
| `at` | number | Unix seconds the device reported. |
| `actor` | value | *Access granted and access denied only.* Who: the user or credential name, empty when the reader could not identify them. |
| `method` | value | *Access triggers only.* How: `nfc`, `pin`, `wave`, `mobile`, `touchpass` or `manual`. |
| `granted` | condition | *Access triggers only.* True when access was allowed. |

### Sensors, the General Ones

| Block | Fires when |
| --- | --- |
| [`sensor activated`](https://docs.rual.nl/block-types/home/homeassist_trigger_sensor_activated) | Any sensor capability transitions false to true: motion detected, a door opened, water found, smoke detected, gas detected, vibration. The extra `sensor type` pin narrows it to one of `occupancy`, `contact`, `water_leak`, `smoke`, `gas`, `vibration`; empty means any of them. |
| [`sensor deactivated`](https://docs.rual.nl/block-types/home/homeassist_trigger_sensor_deactivated) | The mirror: a sensor capability transitions true to false. |

"Sensor" here means the binary capabilities that describe the world. It excludes `power`, which has its own light and switch triggers, and excludes `tamper` and `battery_low`, which are device health. Folding `power` in would make every whole-house sensor flow fire on its own lighting changes and livelock.

On `sensor deactivated`, remember whose delay you are seeing. A motion sensor clears after *its own* configured stillness timeout, often 60 to 120 seconds, so this fires when the sensor says clear, not when the room empties.

[sensor activated](https://docs.rual.nl/block-types/home/homeassist_trigger_sensor_activated) starts the flow when a sensor turns ON, and the two [value](https://docs.rual.nl/block-types/value/value_default) blocks are what narrow it: sensor type keeps it to leak detectors, room keeps it to the basement. Both are optional, and leaving them empty makes this one block the whole-house version. It fires on the TRANSITION, so a sensor sitting in a puddle and re-reporting fires the flow once, and the flow shuts the valve with [switch off](https://docs.rual.nl/block-types/home/homeassist_switch_off).

![Studio canvas example for the sensor activated block: one trigger for every sensor of one kind.](https://docs.rual.nl/canvas-examples/homeassist_trigger_sensor_activated.png)

### Presence and Access

| Block | Fires when |
| --- | --- |
| [`presence detected`](https://docs.rual.nl/block-types/home/homeassist_trigger_presence_detected) | `occupancy` goes false to true. Equivalent to `sensor activated` with the sensor type set to occupancy, and it exists as its own block because it is the single most common automation there is. |
| [`presence cleared`](https://docs.rual.nl/block-types/home/homeassist_trigger_presence_cleared) | `occupancy` goes true to false. The delay before this fires is the sensor's, not the platform's. |
| [`contact opened`](https://docs.rual.nl/block-types/home/homeassist_trigger_contact_opened) | `contact` goes true, which means **open**. Zigbee and Matter both report contact true for a *closed* door on the wire; that inversion is corrected at the adapter, so `value` here is true when the door is open. |
| [`contact closed`](https://docs.rual.nl/block-types/home/homeassist_trigger_contact_closed) | `contact` goes false, meaning closed. |

### Alarms

| Block | Fires when |
| --- | --- |
| [`water leak detected`](https://docs.rual.nl/block-types/home/homeassist_trigger_water_leak) | `water_leak` transitions to true. On the transition, so a sensor sitting in a puddle and re-reporting fires once. |
| [`smoke detected`](https://docs.rual.nl/block-types/home/homeassist_trigger_smoke_detected) | `smoke` transitions to true. |

**These supplement a certified alarm, they do not replace one.** They depend on a mesh, a broker and a node all being up. Gas has no dedicated trigger; use `sensor activated` with the sensor type set to `gas`.

### Lights and Switches

| Block | Fires when |
| --- | --- |
| [`light turned on`](https://docs.rual.nl/block-types/home/homeassist_trigger_light_on) | A device classified as a **light** transitions `power` false to true. |
| [`light turned off`](https://docs.rual.nl/block-types/home/homeassist_trigger_light_off) | The same device type transitions `power` true to false. |
| [`switch turned on`](https://docs.rual.nl/block-types/home/homeassist_trigger_switch_on) | A device classified as a **switch** transitions `power` false to true. |
| [`switch turned off`](https://docs.rual.nl/block-types/home/homeassist_trigger_switch_off) | The same, true to false. |

**These fire for changes RUAL did not make.** A physical wall switch, the Hue app, a scene fired by a dimmer: the radio reports the new state and the trigger fires. That is usually the point, and it is occasionally a trap, because a flow that turns a light on in response to a light turning on is a loop.

The light/switch split follows the derived device type, not the vendor's opinion. A device with on/off but no brightness or colour is a switch, so metering plugs land here rather than under lights, even when the vendor announces them as lights.

### Buttons and Scene Controllers

| Block | Fires when |
| --- | --- |
| [`button pressed`](https://docs.rual.nl/block-types/home/homeassist_trigger_button_pressed) | Any momentary control emits an action: Aqara buttons, Hue dimmers, scene switches. Fires on **every** press, including a repeat of the same press type. |

Branch on the `value` pin to tell single from double from hold. **The exact strings are the device's own.** Common ones are `single`, `double`, `hold` and `release`, but a four-button remote sends things like `1_single` and a Hue dimmer sends `initial_press` and `long_release`. Press the button once with the Zigbee2MQTT frontend open, or read the device state through `get device`, and use what it actually sends rather than what it ought to.

### Environment Readings

| Block | Extra pins | Fires when |
| --- | --- | --- |
| [`temperature changed`](https://docs.rual.nl/block-types/home/homeassist_trigger_temperature_changed) | `above`, `below`, in °C | The `temperature` capability changes. With a threshold set, only on the crossing. |
| [`humidity changed`](https://docs.rual.nl/block-types/home/homeassist_trigger_humidity_changed) | `above`, `below`, in % | The `humidity` capability changes. Same crossing semantics. |

Other numeric capabilities have no dedicated trigger. For illuminance, CO₂, power draw or a blind's position, use `device state changed` and branch on the `capability` pin, or read the value with `get device state` inside a flow another trigger started. The threshold-crossing behaviour is specific to these two blocks.

### Device Health and the Fleet

| Block | Fires when |
| --- | --- |
| [`battery low`](https://docs.rual.nl/block-types/home/homeassist_trigger_battery_low) | The battery percentage crosses below the `below %` pin, default 20, **or** the device raises its own `battery_low` flag. Both dialects are handled because devices implement one or the other and rarely both. |
| [`device unavailable`](https://docs.rual.nl/block-types/home/homeassist_trigger_device_unavailable) | A device transitions to unreachable: a flat battery, out of range, a bulb switched off at the wall. It does **not** repeat while the device stays away. There is no matching "device came back" trigger; use `get status` or a periodic `get all devices` sweep if you need one. |
| [`device discovered`](https://docs.rual.nl/block-types/home/homeassist_trigger_device_discovered) | A new device appears on the radio, before it is adopted. Fires once per device, the first time it is ever seen. **The one trigger that fires for unadopted devices.** |
| [`device adopted`](https://docs.rual.nl/block-types/home/homeassist_trigger_device_adopted) | A device is taken into service, whether by the API, the studio or a flow calling `device adopt`. Use it to set a room, apply defaults or announce the new device. |

### The Catch-All

| Block | Fires when |
| --- | --- |
| [`device state changed`](https://docs.rual.nl/block-types/home/homeassist_trigger_device_state_changed) | Any capability of any adopted device changes. |

**High volume by design.** A house with a dozen sensors produces hundreds of these an hour. Use it for logging, and for capabilities that have no dedicated trigger, then branch on the `capability` pin. Where a specific trigger exists, use the specific trigger: it is cheaper and it says what it means on the canvas.

### Doors, Access Control and Cameras

These eight arrive with the UniFi Access and Protect adapter, and they are the automations people actually want from a door: when someone rings, when a card is swiped, when the gate opens, when an unknown card is refused at three in the morning.

They ship with [rual-core PR 312](https://github.com/rual/rual-core/pull/312), which is open at the time of writing, so unlike the blocks above they do not have block pages here yet. Those appear on their own once the block catalog syncs.

| Block | Extra pins | Fires when |
| --- | --- | --- |
| `doorbell rang` |  | Somebody rings a doorbell or door station, from a UniFi door station or a Protect doorbell camera. Momentary: there is no "still ringing" state. |
| `door or gate opened` |  | `door_position` transitions to true, meaning open. This is the physical position, **not** the lock: a door can be unlocked and shut. |
| `door or gate closed` |  | `door_position` transitions to false. Pair it with a delay for "the gate has been open too long". |
| `access granted` | `method` | Somebody is let in: a valid card, PIN, wave or mobile credential. The `method` pin narrows it to one of `nfc`, `pin`, `wave`, `mobile`, `touchpass`. Does not fire on a refusal. |
| `access denied` |  | Somebody is refused: an unknown card, a wrong PIN, a credential outside its schedule. |
| `NFC card swiped` | `card` | A card or fob is presented, regardless of the decision that follows. The `card` pin narrows it to one token or actor. |
| `camera motion detected` |  | A UniFi Protect camera detects motion. One event per detection, not a sustained state. |
| `security alarm activated` |  | The controller's own alarm transitions to active. Distinct from the smoke, gas and leak triggers, which are individual detectors. |

**The two entry triggers carry three extra out-pins**, because "someone came in" is only useful with who and how: `actor` (the user or credential name, empty when the reader could not identify them), `method`, and `granted`. They sit beside the standard pins rather than buried in the device object.

**Swiped and granted are different questions.** `NFC card swiped` fires when the card is presented, before and regardless of the decision, which is what you want for "when my gym fob is tapped, turn on the porch light". `access granted` fires only when the door actually opened to somebody. A refused card fires the first and not the second.

**One caveat on repeats.** Doorbell and camera motion events carry the moment they happened, so consecutive events differ and each one fires; two in the same second collapse into one. NFC carries the card token, so the *same* card presented twice in a row with nothing in between does not fire the second time. If you need every tap counted, read the access event instead, which carries the full payload.

### How Dispatch Actually Works

Two properties are worth knowing because they explain both the speed and the limits.

**Dispatch is in process, not queued.** Every other trigger in RUAL goes through the queue, which buys durability at the cost of a write and a poll interval. That trade is wrong here: the entire value of "when motion is detected, turn on the light" is that it happens before you have finished walking through the door, and a queue round trip is hundreds of milliseconds on a good day. The cost is that a node restart drops in-flight triggers, which is the correct behaviour anyway. Replaying a motion event from before a restart would turn a light on for somebody who left ten minutes ago.

**The trigger index is cached, not searched per event.** A single occupancy sensor produces tens of events a minute, and searching your blueprints on each one would make a busy house the node's dominant read load. The index is built at startup, before the adapters connect, so the first motion event after a restart is not dropped into an empty index. It is rebuilt when you save a blueprint, over the same channel that invalidates the blueprint cache, so a studio edit takes effect without a restart.

**Concurrent runs are capped at 16.** Beyond that, events are dropped with a warning in the log rather than queued: a backlog of stale "turn the light on" commands is worse than none. Sixteen is sized for a house, and hitting it usually means a flow is slow rather than that the house is busy. Only *active* trigger blocks in *saved* blueprints are indexed.

### Next Steps

### Frequently asked

**Does a RUAL motion trigger fire every time the sensor reports?**

No, it fires on the change. A motion sensor re-announces occupancy every thirty seconds or so while somebody is in the room, and the trigger requires the value to have changed, so the flow runs once, on entry. Button presses are the deliberate exception, because a momentary pulse has no meaningful unchanged state.

**How does a temperature threshold trigger work in RUAL?**

It fires on the crossing, not the level. A trigger set to above 25 fires once when the room passes 25 degrees, not once per reading for the rest of the afternoon, and it fires again only after the reading has dropped back below 25 and crossed up again. A first-ever reading above the threshold counts as a crossing.

**How do I write one RUAL automation for every sensor in a room?**

Leave the trigger's device pin empty and set its room pin. The device pin is optional precisely so that one block can be the whole-room or whole-house version; requiring it would force one flow per sensor.
