Local Home Automation
Zigbee, Thread/Matter, Z-Wave, Philips Hue and Daikin on a RUAL Core Nano: which radio to buy, the daemon stack around the node, adoption, and the blocks that drive the devices.
A RUAL Core Nano can automate the building it sits in: lights, sensors, switches and climate, driven by the same blueprints and the same document model as everything else on the node, with no vendor cloud in the loop. This tutorial is the whole setup on one box: which radio to buy, the daemons that sit between the node and the radios, how devices get adopted, and the blocks you build with. It assumes the node itself is already running; see RUAL Core Nano for the machine and the base configuration.
Which Radio to Buy
Zigbee, Thread and Z-Wave are three separate radios on three different frequency bands. Nothing bridges them, and no single dongle does all three. What you need depends entirely on what your devices speak:
| Devices | Protocol | Radio needed |
|---|---|---|
| Philips Hue bulbs | Zigbee (or via a Hue bridge over LAN) | Zigbee dongle, or nothing if you keep the bridge |
| Aqara sensors, buttons, plugs | Zigbee (newer models: Thread/Matter) | Zigbee dongle |
| IKEA Trådfri | Zigbee | Zigbee dongle |
| Eve, newer Nanoleaf, newer Aqara | Thread (Matter) | Thread dongle + Matter Server |
| Fibaro, Aeotec, Shelly Wave | Z-Wave | Z-Wave dongle |
| Daikin air conditioning | WiFi / LAN | None, it is an IP device |
Buy First: Home Assistant Connect ZBT-2
Silicon Labs MG24 based, and the right first purchase for almost everyone: Hue and Aqara are the two ecosystems most people start from, and both are Zigbee. Flash it with Zigbee firmware, run Zigbee2MQTT against it, and RUAL sees every device on the mesh.
One radio at a time. The ZBT-2 hardware can do Zigbee or Thread, but not both simultaneously. Home Assistant tested that ("multiprotocol" / MultiPAN) thoroughly on the previous ZBT-1, found it caused device instability, and explicitly does not support it. They recommend dedicating a dongle per protocol. So if you want Zigbee and Thread, buy two ZBT-2s.
Buy Second, If You Have Z-Wave Devices: Home Assistant Connect ZWA-2
Z-Wave 800 series (Silicon Labs ZG23), with Z-Wave Long Range support and a foot-long antenna tuned for the sub-GHz band. Runs alongside a ZBT-2 with no conflict whatsoever: different frequency band (868 MHz in the EU vs 2.4 GHz), different USB device, different daemon.
Note that none of Hue, Aqara or Daikin are Z-Wave. If you do not already own Z-Wave hardware, this is not your first purchase.
Running Both
Two dongles on one machine is the ordinary configuration, not an exotic one. The M2 Mac Mini has exactly two USB-A ports, so two dongles fit natively. The M4 Mac Mini has no USB-A ports (front USB-C, rear Thunderbolt), so there they hang off a powered USB-C hub. Either way, RUAL namespaces every device by its adapter (zigbee:0x00158d..., zwave:12, matter:4), so nothing collides and one device picker shows them all.
Use the extension cables. Both dongles ship with one, and it is the single most common cause of a Zigbee mesh that "works but drops devices". USB 3.0 ports and NVMe SSDs emit broadband noise right across 2.4 GHz; a Zigbee coordinator plugged directly into the back of a machine sits inside that. Half a metre of cable, away from the case, fixes it. On a hub the same rule applies: dongles on their cables, not packed into adjacent ports. The ZWA-2's antenna wants to be vertical, and has an accelerometer specifically to nag you about that.
The Stack Around the Node
RUAL does not drive the radios itself. A native Zigbee stack means owning EZSP/Z-Stack framing, the ZCL cluster library, mesh routing, OTA updates and a device quirks database: years of work whose output would be a worse Zigbee2MQTT. So the node sits behind the reference daemon for each protocol:
| Protocol | Daemon | How RUAL reaches it |
|---|---|---|
| Zigbee | Zigbee2MQTT | MQTT |
| Z-Wave | Z-Wave JS UI | MQTT (gateway type must be ValueID topics) |
| Thread / Matter | Matter Server + a Thread Border Router | WebSocket |
| Philips Hue | the bridge itself | HTTPS + event stream, on the LAN |
| Daikin | the BRP WiFi adapter itself | HTTP, on the LAN |
This is also where the device knowledge base comes from. Zigbee2MQTT publishes a complete machine-readable description of every device on your mesh, covering every attribute it reports with its type, unit and range. Roughly 4,000 devices, maintained by people who own the hardware. RUAL reads that directly, so pairing a sensor released after your RUAL version still gives you a fully described device. There is no device list in RUAL to update.
You will also need Redis and an MQTT broker (Mosquitto). Both are small. On a Mac Mini, running the daemons in Docker Desktop and the node natively is the least fuss.
Configuration
Home automation is off by default. A node without [homeassist] enabled = true starts no radio goroutine, creates no device store, and every home-automation block reports HOMEASSIST_NOT_AVAILABLE, so a cloud node pays nothing for these blocks existing. Turn it on in the node configuration:
[homeassist]
enabled = true
entity = 1
[homeassist.zigbee]
broker = "tcp://127.0.0.1:1883"
base_topic = "zigbee2mqtt"
Discovery and Adoption
Devices appear on their own. A Zigbee coordinator hears everything ever paired to its network, and the node records each one as it reports. But a discovered device is not in service yet: it fires no triggers and refuses every command until you adopt it.
That gate is the whole reason this works. Without it, "when any sensor activates" means "when anything within thirty metres twitches", including the sensor somebody paired to test three years ago and the devices a second-hand Z-Wave stick inherited from its previous owner. Adoption is what turns a radio's earshot into a system you can reason about.
The onboarding loop:
- Open the radio for joining, with the
pairing openblock orPOST /_system/homeassist/pair. The window is bounded bypairing_window; a permanently joinable coordinator is how a mesh acquires devices nobody added. - Put the device into pairing mode (usually a long press).
- It appears as discovered. The
device discoveredtrigger fires. It is the one trigger that fires for unadopted devices, because noticing them is its purpose. - Adopt it, and give it a name and a room. From that moment its triggers fire and blocks can command it.
You can automate step 4: wire device discovered straight into device adopt. For a bench mesh, auto_adopt = true skips it entirely, but leave that off anywhere real.
Blocks
Every device, whatever protocol it speaks, is described in one vocabulary. A motion sensor is occupancy whether it is an Aqara on Zigbee, a Fibaro on Z-Wave or an Eve on Thread, so a flow written against one works against the others.
Two vendor quirks are corrected at the boundary rather than left to you: contact sensors read true when the door is OPEN (Zigbee and Matter both report the opposite on the wire), and colour temperature is in kelvin, not mireds.
Triggers: These Start a Flow
| Block | Fires when |
|---|---|
sensor activated / sensor deactivated | Any sensor turns on or off. Narrow it with the sensor type pin, or leave it open for the whole house. |
presence detected / presence cleared | Occupancy specifically. |
contact opened / contact closed | A door or window. |
light turned on / light turned off | A light changes state, including from a wall switch or the vendor's own app. |
switch turned on / switch turned off | A plug or relay. |
button pressed | A button or scene switch. The value pin says single, double or hold. |
water leak detected, smoke detected | Alarm sensors. |
temperature changed, humidity changed | A reading changes, or crosses a threshold you set. |
battery low, device unavailable | Device health. |
device discovered, device adopted | The fleet changes. |
device state changed | Anything at all. High volume, so use a specific trigger where one exists. |
Transition triggers fire on the change, not on every report. A motion sensor re-announcing occupancy: true every thirty seconds while somebody stands in the hallway fires your flow once, on entry. Threshold triggers likewise fire on the crossing: "above 25 °C" fires once when the room passes 25, not every minute for the rest of the afternoon.
Actions
| Block | Does |
|---|---|
light on | Turns a light on, optionally setting brightness, kelvin and colour in the same command, so the lamp transitions once instead of visibly stepping through full white first. |
light off, light toggle, light dim | The rest of the lighting set. |
switch on / switch off | Plugs and relays. |
climate set | Mode, target temperature and fan speed on an air conditioner or thermostat. |
device set | Any capability by name: blind position, lock state, anything the specific blocks do not cover. |
get all devices, get active sensors, get lights that are on, get rooms | Query the registry. These read memory, make no network calls, and are cheap enough to call in a loop. |
get device, get device state | One device, or one capability of one device. |
device adopt, device rename, device forget, pairing open | Fleet management from a flow. |
get status | Which radios are connected. A bridge that has gone away otherwise looks exactly like a quiet house. |
History and Energy
The node records a bounded history of what its devices do, and the word doing the work is bounded. A dozen sensors produce tens of reports a minute. A motion sensor re-announces while you are in the room, a power meter reports every ten seconds, every Zigbee message carries a link-quality stamp. Storing each one would be millions of rows a month whose information content is nearly zero.
So nothing stores what a device said. It stores what changed:
| Kind | Examples | Stored | Charted as |
|---|---|---|---|
| Alarm | occupancy, contact, leak, smoke | On transitions. A door open for eight hours is two rows, not four hundred and eighty. | Timeline; summarised as how long it was true |
| Measurement | temperature, humidity, power draw, lux | Past a per-capability deadband, plus a heartbeat. A thermometer drifting 0.02 °C stores nothing; a room warming up stores a curve. | Line; min / average / max |
| Meter | energy | At a fixed interval, because a lifetime counter changes on every report. | Bars of consumption per window, never the raw total |
| Event | button presses | Every one, because every occurrence is the data. | not charted |
| State | on/off, brightness, mode | On change. | Steps |
The heartbeat is worth understanding: without it, a deadband makes a stable sensor indistinguishable from a dead one, because both produce no rows. With it, a flat line is visibly flat and a gap is visibly a gap.
Two things this makes possible. The get device history block returns raw readings and, on request, aggregated windows, so a flow can answer "was anyone in the hall between two and four" or "how much did the boiler use last week" without an external time-series database. And RUAL Studio's Devices → Home Automation screen charts it: per-capability history with 1h/24h/7d/30d ranges, and an energy panel showing consumption per device.
Retention defaults to 90 days and readings are pruned hourly. Set [homeassist.analytics] enabled = false to record nothing; the history block then reports recording = false, which is deliberately distinguishable from an empty result.
What to Know Before You Commit
- Device state is not persisted across restarts. Which devices exist, their names, rooms and adoption all survive; what they were doing does not. For the first few seconds after a restart, "which lights are on" honestly answers "I have not been told yet" rather than guessing. Blocks report
state_knownso a flow can tell the difference. - Triggers are not durable. Dispatch is in-process, so a restart drops anything in flight. That is deliberate: replaying a motion event from before the restart would light a room somebody left ten minutes ago.
- Smoke and leak triggers are a supplement, not a certified alarm. They depend on a mesh, a broker and a node all being up.
- Daikin is polled, not pushed. Its local API has no event channel, so
poll_interval(default 30s) is the resolution of every climate trigger. The newer BRP069C4x "Onecta" firmware uses a different protocol and is not supported yet. - Matter endpoints collapse. A Matter device with two endpoints, such as a two-gang switch, currently appears as one device.
- History is not a metering-grade record. It is sampled by design, so it answers trends and totals well and forensic questions poorly. If you need every reading, for billing or for compliance, write them to a storage from a flow instead.
A First Flow
Hallway light on when someone walks in, off when they leave, but only after dark:
presence detected, roomHallway→get device stateon the hallway sensor, capabilityilluminance.ifthe reading is below 30 lux →light on, brightness 40, kelvin 2700.- Separately:
presence cleared, roomHallway→light off.
Both triggers fire on the transition, so neither re-runs while somebody stands still. That is four blocks, and it is the shape most home automation takes.
