Bluez (bluez v0.1.0)

Copy Markdown View Source

Brings up the Linux BlueZ stack — dbus-daemon, bluetoothd, and (optionally) bluez-alsa — under one supervisor, with Elixir clients for BLE scanning (Bluez.Client), active GATT connections (Bluez.Gatt), pairing (Bluez.Agent), and A2DP audio PCM discovery (Bluez.BlueAlsa).

Built for Nerves devices (read-only rootfs, MuonTrap-supervised daemons) but has no Nerves dependency; any Linux host where the calling application may own the system D-Bus instance works.

Children, started :rest_for_one:

  1. dbus-daemon --system (MuonTrap.Daemon) — owns the system bus at /run/dbus/system_bus_socket.
  2. Bluez.BusReady — a one-line gate that blocks until the bus socket exists, so bluetoothd never races the bus. :rest_for_one re-runs it (and bluetoothd) if dbus-daemon restarts.
  3. bluetoothd (MuonTrap.Daemon) — claims org.bluez, drives the adapter via the kernel mgmt socket. Run with -E (experimental) — required for AdvertisementMonitorManager1 passive scanning and the GATT MTU property.
  4. Bluez.Client — persistent rebus client owning the discovery session; adverts fan out through its on_advertisement: fun.
  5. Bluez.Agent — default NoInputNoOutput pairing agent.
  6. Bluez.Gatt (+ its Task.Supervisor) — active connections + GATT client; results flow through its on_gatt_event: fun.
  7. With audio: true (the default): bluealsad (MuonTrap.Daemon, A2DP source) and Bluez.BlueAlsa (org.bluealsa client). Placed after the scanning/GATT clients so an audio-daemon fault never restarts the scanning stack — the children that follow do restart with it under :rest_for_one, which is intended (same audio path).
  8. extra_children: — host-supplied child specs, appended last.

Options

All are optional; pass them to start_link/1 (usually via a child spec {Bluez, opts}):

  • client: — keyword opts for Bluez.Client (on_advertisement:, pubsub:).
  • gatt: — keyword opts for Bluez.Gatt (on_gatt_event:, on_connections_changed:).
  • audio: — boolean (default true): start the bluealsad daemon + Bluez.BlueAlsa client. Requires bluez-alsa (v4 recommended) on the device.
  • blue_alsa: — keyword opts for Bluez.BlueAlsa (pubsub:).
  • extra_children: — host child specs appended at the end of the tree. Under :rest_for_one they restart with the audio path, and a fault there never disturbs the scanning/GATT stack above them. Ordering within the slot is the caller's contract.
  • desired_adapter: — MAC string ("AA:BB:CC:DD:EE:FF") of the radio to drive, or nil (auto: lowest-index adapter). Written to :persistent_term before the children start. Hosts that switch radios at runtime may instead publish the term themselves under Bluez.DevicePath.desired_adapter_key/0 BEFORE (re)starting this supervisor — the opt and the pre-published term coexist (the opt, when present, wins by writing last).
  • dbus_daemon_path: — dbus-daemon binary (default /usr/bin/dbus-daemon).
  • bluetoothd_path: — bluetoothd binary (default /usr/libexec/bluetooth/bluetoothd).
  • bluealsad_paths: — candidate bluez-alsa daemon binaries, first existing wins (default ["/usr/bin/bluealsad", "/usr/bin/bluealsa"] — the daemon was renamed in bluez-alsa v4).

Runtime requirements

  • BlueZ ≥ 5.66 (bluetoothd -E), dbus, and — for audio: true — bluez-alsa v4.
  • Writable /run/dbus and /data/bluetooth (prepare_runtime/0 creates both and a machine-id before the daemons launch; on a read-only rootfs, point /var/lib/bluetooth at /data/bluetooth via an overlay symlink so bluetoothd can persist adapter state).
  • This supervisor OWNS the system bus: don't run it next to a distro dbus/bluetoothd.

Public-API catch :exit idiom

The synchronous read APIs (Bluez.Client.adapters_info/0, Bluez.BlueAlsa.pcms/0, Bluez.Gatt.connections_free/0, …) are meant to be wrapped by hosts in catch :exit so callers work while the stack is down. Be aware what that swallows: it converts BOTH the process-not-running exit AND a call timeout into the same "subsystem off" default — a wedged server renders as a disabled subsystem rather than raising. Accept that tradeoff knowingly, or catch only :exit, {:timeout, _} separately where the distinction matters.

Summary

Functions

Returns a specification to start this module under a supervisor.

The child list init/1 supervises, exposed for child-order tests. See the moduledoc for the supported opts.

Create the writable dirs the daemons need before they launch. Idempotent; safe to call on every (re)start of this supervisor (init/1 does).

System-bus socket path the rebus clients connect to.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

children(opts)

@spec children(keyword()) :: [Supervisor.child_spec() | {module(), term()} | module()]

The child list init/1 supervises, exposed for child-order tests. See the moduledoc for the supported opts.

prepare_runtime()

@spec prepare_runtime() :: :ok

Create the writable dirs the daemons need before they launch. Idempotent; safe to call on every (re)start of this supervisor (init/1 does).

socket_path()

@spec socket_path() :: String.t()

System-bus socket path the rebus clients connect to.

start_link(opts \\ [])