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:

DevicesProtocolRadio needed
Philips Hue bulbsZigbee (or via a Hue bridge over LAN)Zigbee dongle, or nothing if you keep the bridge
Aqara sensors, buttons, plugsZigbee (newer models: Thread/Matter)Zigbee dongle
IKEA TrådfriZigbeeZigbee dongle
Eve, newer Nanoleaf, newer AqaraThread (Matter)Thread dongle + Matter Server
Fibaro, Aeotec, Shelly WaveZ-WaveZ-Wave dongle
Daikin air conditioningWiFi / LANNone, 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:

ProtocolDaemonHow RUAL reaches it
ZigbeeZigbee2MQTTMQTT
Z-WaveZ-Wave JS UIMQTT (gateway type must be ValueID topics)
Thread / MatterMatter Server + a Thread Border RouterWebSocket
Philips Huethe bridge itselfHTTPS + event stream, on the LAN
Daikinthe BRP WiFi adapter itselfHTTP, 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:

  1. Open the radio for joining, with the pairing open block or POST /_system/homeassist/pair. The window is bounded by pairing_window; a permanently joinable coordinator is how a mesh acquires devices nobody added.
  2. Put the device into pairing mode (usually a long press).
  3. It appears as discovered. The device discovered trigger fires. It is the one trigger that fires for unadopted devices, because noticing them is its purpose.
  4. 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

BlockFires when
sensor activated / sensor deactivatedAny sensor turns on or off. Narrow it with the sensor type pin, or leave it open for the whole house.
presence detected / presence clearedOccupancy specifically.
contact opened / contact closedA door or window.
light turned on / light turned offA light changes state, including from a wall switch or the vendor's own app.
switch turned on / switch turned offA plug or relay.
button pressedA button or scene switch. The value pin says single, double or hold.
water leak detected, smoke detectedAlarm sensors.
temperature changed, humidity changedA reading changes, or crosses a threshold you set.
battery low, device unavailableDevice health.
device discovered, device adoptedThe fleet changes.
device state changedAnything 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

BlockDoes
light onTurns 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 dimThe rest of the lighting set.
switch on / switch offPlugs and relays.
climate setMode, target temperature and fan speed on an air conditioner or thermostat.
device setAny 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 roomsQuery the registry. These read memory, make no network calls, and are cheap enough to call in a loop.
get device, get device stateOne device, or one capability of one device.
device adopt, device rename, device forget, pairing openFleet management from a flow.
get statusWhich 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:

KindExamplesStoredCharted as
Alarmoccupancy, contact, leak, smokeOn transitions. A door open for eight hours is two rows, not four hundred and eighty.Timeline; summarised as how long it was true
Measurementtemperature, humidity, power draw, luxPast 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
MeterenergyAt a fixed interval, because a lifetime counter changes on every report.Bars of consumption per window, never the raw total
Eventbutton pressesEvery one, because every occurrence is the data.not charted
Stateon/off, brightness, modeOn 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_known so 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:

  1. presence detected, room Hallwayget device state on the hallway sensor, capability illuminance.
  2. if the reading is below 30 lux → light on, brightness 40, kelvin 2700.
  3. Separately: presence cleared, room Hallwaylight 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.

Next Steps

RUAL Core Nano The deployment shape this tutorial runs on. All Blocks The full catalog, including every home-automation block. Repeating Events Automation on a clock instead of a sensor.