A from-scratch, pure-Elixir Zigbee stack.
This module is the backend-agnostic public API. You open a chip backend (which
implements Zigbee.Adapter) and then drive it through these functions. The
same calls work no matter which radio is underneath.
Quick start
Open the radio, form a coordinator network, then pair and read a sensor:
# 1. open the dongle (Silicon Labs EmberZNet backend) and become its subscriber
{:ok, zb} = Zigbee.start_link(Zigbee.EZSP.Adapter, device: "/dev/ttyACM0", speed: 460_800)
:ok = Zigbee.subscribe(zb, self())
# 2. form a coordinator network (also registers the default HA endpoint)
{:ok, params} = Zigbee.form_network(zb, channel: 15)
# 3. open joining and wait for a device (put the device into pairing mode now)
{:ok, dev} = Zigbee.Interview.open_and_wait(zb)
# 4. interview it: enumerate endpoints, bind + configure reporting for temp/humidity
{:ok, _report} = Zigbee.Interview.run(zb, dev.node_id, dev.eui64)
# 5. watch the readings roll in (°C / %)
Zigbee.Interview.collect(zb, 60_000)
#=> [%{cluster: 0x0402, endpoint: 1, value: 21.4, unit: "°C"}, ...]Events
The subscriber (set with subscribe/2) receives backend-neutral events:
{:zigbee, :device_joined, %{node_id: _, eui64: _}}
{:zigbee, :message, %Zigbee.Message{}}Zigbee.Interview consumes these for you; consume them yourself only for custom
flows. See Zigbee.Interview for pairing, Zigbee.ZCL / Zigbee.ZDO for the
wire codecs, and Zigbee.Adapter for writing a new radio backend.
Summary
Functions
Register an application endpoint. Rarely needed directly; form_network/2
registers the default endpoint. Must be called before the network is up.
Form a centralized (trust-center) coordinator network and return its parameters.
The coordinator's own identifier: its 64-bit IEEE 802.15.4 extended address (EUI64), the radio's permanent, globally-unique hardware address, like a MAC address. Returned as a raw 8-byte little-endian binary (wire order).
Radio/stack info (e.g. %{protocol_version: 13, stack_version: "7.5.1.0"}).
Open the network for joining for seconds (0..254; 255 = no timeout).
Re-establish the network already stored on the radio (via networkInit),
re-registering endpoints first. Returns {:ok, params}, or {:error, :no_network}
if nothing is stored. Use this on restart (not form_network/2): forming makes a
new network (new key) and orphans already-paired devices.
Re-establish the stored network, or form a fresh one if there is none. The right bring-up for a long-running coordinator: rejoins existing devices when possible, forms only on first run. See "Persistence & restart" in the README.
Reset the coordinator: leave / tear down the current network, clearing the
network state stored on the radio. The next bring-up must form_network/2.
Send a direct APS unicast to node_id and return {:ok, aps_seq}. payload is
a raw APS payload. Build it with Zigbee.ZCL (on an application profile like
0x0104) or Zigbee.ZDO (on profile 0x0000). opts: :src_endpoint
(default 1). Zigbee.Interview uses this under the hood.
Start a radio backend (a module implementing Zigbee.Adapter, e.g.
Zigbee.EZSP.Adapter) and wrap it in a handle used by every other function here.
Register pid (default: the caller) to receive the normalized {:zigbee, _}
events. Do this before Zigbee.Interview calls so join/message events reach it.
Wrap an already-started backend process in a handle.
Functions
Register an application endpoint. Rarely needed directly; form_network/2
registers the default endpoint. Must be called before the network is up.
Form a centralized (trust-center) coordinator network and return its parameters.
Options (all optional): :channel (11..26, default 15), :pan_id,
:extended_pan_id (8 bytes), :tx_power (dBm), :network_key (16 bytes), and
:endpoints (:default registers HA endpoint 1, :none, or a list of
{endpoint, profile, device_id, in_clusters, out_clusters}). Endpoints are
registered here because they must exist before the network comes up.
The coordinator's own identifier: its 64-bit IEEE 802.15.4 extended address (EUI64), the radio's permanent, globally-unique hardware address, like a MAC address. Returned as a raw 8-byte little-endian binary (wire order).
Unlike a node's 16-bit network address (the coordinator's is always 0x0000),
the identifier never changes. Devices bind their clusters to it, so it's the
stable identity that lets reestablish_network/2 bring paired devices back after
a restart with no re-pairing.
Radio/stack info (e.g. %{protocol_version: 13, stack_version: "7.5.1.0"}).
Open the network for joining for seconds (0..254; 255 = no timeout).
Re-establish the network already stored on the radio (via networkInit),
re-registering endpoints first. Returns {:ok, params}, or {:error, :no_network}
if nothing is stored. Use this on restart (not form_network/2): forming makes a
new network (new key) and orphans already-paired devices.
Re-establish the stored network, or form a fresh one if there is none. The right bring-up for a long-running coordinator: rejoins existing devices when possible, forms only on first run. See "Persistence & restart" in the README.
Reset the coordinator: leave / tear down the current network, clearing the
network state stored on the radio. The next bring-up must form_network/2.
Send a direct APS unicast to node_id and return {:ok, aps_seq}. payload is
a raw APS payload. Build it with Zigbee.ZCL (on an application profile like
0x0104) or Zigbee.ZDO (on profile 0x0000). opts: :src_endpoint
(default 1). Zigbee.Interview uses this under the hood.
@spec start_link( module(), keyword() ) :: {:ok, Zigbee.Adapter.t()} | {:error, term()}
Start a radio backend (a module implementing Zigbee.Adapter, e.g.
Zigbee.EZSP.Adapter) and wrap it in a handle used by every other function here.
opts are passed to the backend. For Zigbee.EZSP.Adapter: :device (serial
port) and :speed (baud, 460_800 for the ZBT-2).
Register pid (default: the caller) to receive the normalized {:zigbee, _}
events. Do this before Zigbee.Interview calls so join/message events reach it.
@spec wrap(module(), Zigbee.Adapter.ref()) :: Zigbee.Adapter.t()
Wrap an already-started backend process in a handle.