Discovering and Adopting Devices
Devices appear on their own, but a discovered device fires no triggers and refuses every command until you adopt it. Open pairing, put the device in pairing mode, adopt it and give it a name and a room.
Adoption decides what is part of your system
A coordinator hears every device in radio range that has ever been paired to its network, including the ones somebody paired to test and forgot. A second-hand Z-Wave stick inherits whatever the previous owner left in its NVM. Without an explicit adopt, "when any sensor activates" means "when anything within thirty metres twitches".
The gate is enforced in the trigger dispatcher and the command path rather than in each block, so a new trigger block cannot forget it. The one exception is device discovered, which fires for unadopted devices because noticing them is its purpose.
| Discovered | Adopted | |
|---|---|---|
| Appears in the device list | Only when you ask for unadopted devices | Yes, by default |
| State is tracked | Yes | Yes |
Fires device discovered | Yes, once | Not applicable |
| Fires any other trigger | No | Yes |
| Accepts a command | No, HOMEASSIST_DEVICE_NOT_ADOPTED | Yes |
| Records history | No | Yes, per the sampling policy |
The onboarding loop
Open the radio for joining
The
pairing openblock, orPOST /_system/homeassist/pair. The duration you ask for is clamped to the node'spairing_window, so a request for an hour becomes two minutes rather than being refused. Pass 0 to close pairing immediately.Put the device into pairing mode
Usually a long press, sometimes a specific number of taps. Check the vendor's instructions.
It appears as discovered
The
device discoveredtrigger fires, once, the first time that device is ever seen.Adopt it, with a name and a room
From that moment its triggers fire and blocks can command it.
The last step can be automated: wire device discovered into device adopt, which takes optional name and room pins. See Example Flows. For a bench mesh, auto_adopt = true in the config skips the gate entirely; keep it off anywhere real.
Adopting by hand
List what is waiting, then take it into service:
# Everything the radio has heard that you have not taken into service.
curl -s 'https://<node>/_system/homeassist/devices?unadopted=1' \
-H 'Authorization: Bearer <token>'
# Adopt it, naming it and placing it in one call.
curl -X POST 'https://<node>/_system/homeassist/devices/zigbee:0x00158d0007e1a2b3/adopt' \
-H 'Authorization: Bearer <token>' \
-d '{"name":"Hallway motion","room":"Hallway"}'- The default list returns adopted devices only;
unadopted=1asks for the rest. - Adoption is idempotent. Adopting an already-adopted device succeeds and reports
newly = false, so a flow can call it without checking first.
Pairing means something different on every radio
The pairing open block takes an adapter name and a duration.
| Adapter | What "open pairing" does |
|---|---|
zigbee |
Permit-join on the coordinator, for the requested seconds. The coordinator enforces the window itself. |
zwave |
Starts inclusion, with S2 security where the device supports it. Z-Wave inclusion is not time-bounded by the controller the way permit-join is, so the node closes the window itself when the duration expires. |
matter |
Opens the fabric's commissioning window so another controller can join. Adding a new device to Matter is not "open a window and wait": it needs the 11-digit pairing code or the MT: string printed on the device, which is a different operation. |
hue |
Creates an application key. A Hue bridge has no inclusion mode: it is already a coordinator with its own devices, and what the node needs is authorisation. The bridge grants it only in the thirty seconds after somebody physically presses the link button. |
daikin |
Not supported, and it says so: HOMEASSIST_PAIRING_UNSUPPORTED. A Daikin unit is on the WiFi or it is not. Add its IP to [homeassist.daikin] hosts. |
unifi |
Also unsupported, for a similar reason. UniFi devices are enrolled in the UniFi app, and the node reaches them through the console's integration APIs afterwards. What RUAL needs is a key per app, not an inclusion window: see Setting Up a RUAL Nano. |
Every device has two identifiers, a name and a room
| Field | Example | What it is for |
|---|---|---|
id | zigbee:0x00158d0007e1a2b3 | The platform identifier, <adapter>:<native>. Stable across restarts and renames, which is why blueprints pin to it. The prefix means two radios can carry the same native address without colliding. |
native_id | 0x00158d0007e1a2b3, 12, a Hue UUID | The address in the adapter's own namespace. What you would type into Zigbee2MQTT or Z-Wave JS UI. |
name | Hallway motion | What a human calls it. Seeded from the vendor's friendly name, then yours. |
room | Hallway | Free text, set by you. Empty until somebody says. |
Any block that addresses a device accepts the platform id, the native id or the exact name, case-insensitively. A name must match exactly: a prefix match would make renaming one device silently re-point another's flows.
- Your name and room are yours. Once set, a re-announce from the radio never overwrites them, even though a Zigbee re-pair resets the vendor's friendly name to the model number. Everything else merges: newly reported capabilities are added, a blank manufacturer or model is filled in.
- Renaming in RUAL does not rename at the radio. The Zigbee2MQTT friendly name and the Hue bridge name are unchanged, so those apps keep showing the old name.
Rooms are free text
There is no room registry. A room exists because at least one device is assigned to it, and get rooms returns the distinct non-empty room names across all adopted devices, sorted. Room filters compare case-insensitively, so "Hallway" and "hallway" are the same room, but "Hall" is a different one.
A trigger with an empty device pin and room = Hallway is "any matching device in the hallway": one block instead of one flow per sensor.
Device knowledge comes off the wire
Zigbee2MQTT publishes its full device definition, the exposes tree covering every attribute a device reports with its type, unit and range, on zigbee2mqtt/bridge/devices, refreshed whenever the mesh changes. It covers roughly 4,000 devices and arrives already matched to the device on your mesh. Z-Wave JS does the same through its own config index.
A small profile table sits on top for devices whose exposes tree is correct but produces a bad default. Aqara sensors are the clearest case, reporting battery, voltage, link quality and device temperature alongside the one attribute anybody automates on. A profile:
- guarantees the useful capabilities are present before the first report of that kind arrives;
- normalises the vendor string ("LUMI" becomes "Aqara");
- overrides the derived type where derivation is wrong, such as a button that would otherwise classify as a sensor.
A device with no profile gets exactly what its exposes tree said, which is almost always right. Profiles are applied on discovery and on adopt, so a device paired before its profile existed picks it up the next time somebody adopts it.
Forget is not unpair
device forget, and DELETE /_system/homeassist/devices/<id>, drop the device from RUAL: its name, room and adoption are discarded. It does not unpair the device from the Zigbee, Z-Wave or Matter network. The device stays on the mesh and reappears, unadopted, on its next report.
- Unpairing is done in Zigbee2MQTT or Z-Wave JS UI, and is not exposed as a block.
- A device that leaves the mesh on its own is marked unavailable and kept registered, so a dead battery does not discard its name, room and adoption.
Identity and state both survive a restart
They live in two different places, for two different reasons.
| Identity | Last-known state | |
|---|---|---|
| What | Which devices exist, name, room, adoption and when, capabilities shown, manufacturer, model, matched profile | The last value per capability: whether the light is on, the last temperature, whether the door is open |
| Where | The document store, alongside everything else on the node | Redis |
| Why there | It is an operator's work and must not be lost | It is hot, small, rewritten constantly and worthless once stale: the profile of a cache, and the opposite of identity |
| Written when | On the changes that matter: discovery, adoption, rename, a new capability | On every reading, coalesced per device and flushed every two seconds |
| Expires | Never | After 30 days, refreshed on each write |
Keeping state out of the document store stops a motion sensor in a busy hallway from becoming the node's dominant write load. One Zigbee message routinely carries occupancy, illuminance, battery and link quality: four changed capabilities and one write.
A restored value is a belief, not an observation
- Restoring state does not set
availableor touchlast_seen.availablestays false until the device actually reports. - Momentary capabilities are excluded from the restored map: replaying the last button press as current state would make it look freshly pressed.
- Forgetting a device deletes its stored state too, so re-adopting one later does not restore a belief from before it was removed.
Why a restart does not fire every sensor
State is restored before the adapters connect, so the first real report has something to compare against and an unchanged re-announce stays unchanged. Independently of that, an unknown previous value seeds silently rather than firing, except for smoke, gas and water_leak, where a missed event is worse than a repeated one. The full rule is on the trigger reference.
In RUAL Studio
The device list, the per-device state and the history charts live under Devices, then Home Automation. That screen covers the onboarding view (which uses the unadopted query), adopt, rename, forget, and a one-capability command, so that having adopted a bulb you can check it responds without building a blueprint.
Every trigger and action block that addresses a single device carries the metadata for an in-editor device picker: which device type to offer, so light on offers lights and not thermostats.
The screen does not poll. Device events are pushed onto the WebSocket bus as they arrive, on the homeassistdevices:updates:<device id> channel for one device and homeassistdevices:updates:all for the fleet. Unlike the trigger dispatcher, the broadcast does not filter on adoption, because the onboarding screen has to show devices that are not adopted yet.
The device API
All of it is under /_system/homeassist and all of it requires the blueprints / simulating scope.
| Route | Does |
|---|---|
GET /status | Per-adapter connection state and device counts. |
GET /devices | The device list. Filters: type, adapter, room, capability, plus unadopted=1 or include_unadopted=1. |
GET /devices/{id} | One device, resolved by id, native id or exact name. |
GET /devices/{id}/history | Recorded readings, and buckets when buckets is set. |
POST /devices/{id}/adopt | Adopt, with an optional name and room body. |
POST /devices/{id}/command | Write one capability. One per call: the caller here is a human moving a slider, not an automation. |
POST /devices/{id}/rename | Set name and/or room. |
DELETE /devices/{id} | Forget. Does not unpair. |
POST /pair | Open or close pairing on one adapter. |
GET /rooms | The distinct room names in use. |
GET /capabilities | The vocabulary itself: sensor capabilities, device types, per-capability units, decimals, axis bounds and chart type, and the adapter names. |
GET /capabilities needs no running service and therefore no node with a radio, because the studio has to populate capability dropdowns while editing a blueprint that will run somewhere else. Units, decimals and chart types come from here too.
Next steps
Trigger Reference All 32 blocks that start a flow, and the three rules that decide how often they fire. Example Flows Including auto-adopting a newly discovered device into a room. Action Reference Adopt, rename, forget and pairing from a flow.Frequently asked
Why does my RUAL device not respond even though it appears in the list?
It is discovered but not adopted. An unadopted device is tracked and its state is followed, but it fires no triggers and refuses every command. Adopt it through the studio, through POST /_system/homeassist/devices/{id}/adopt, or from a flow with the device adopt block.
Why does RUAL make me adopt devices instead of using all of them?
A coordinator hears every device ever paired to its network, including ones somebody paired to test years ago, and a second-hand Z-Wave stick inherits whatever the previous owner left on it. Without an explicit adopt, a trigger like when any sensor activates would fire on hardware nobody remembers installing.
Does forgetting a device in RUAL unpair it from the radio?
No. Forget removes the device from RUAL, discarding its name, room and adoption, and the device stays paired to the Zigbee, Z-Wave or Matter network and reappears unadopted on its next report. Unpairing is done in Zigbee2MQTT or Z-Wave JS UI, because stop automating this and evict this from my mesh are different intentions.