Bluez.DevicePath (bluez v0.1.0)

Copy Markdown View Source

Convert between wire BLE addresses and BlueZ device object paths.

ESPHome-style hosts identify peripherals by the 48-bit MAC packed MSB-first into a uint64 (0xAABBCCDDEEFF); BlueZ identifies them by object path (/org/bluez/hciX/dev_AA_BB_CC_DD_EE_FF). Host-testable; the only process-external input is the adapter path below.

Adapter path

Which hciX the whole BlueZ subtree drives is resolved in two steps (the kernel exposes no BT MAC in sysfs, so the MAC → adapter mapping only exists once bluetoothd answers):

  1. The host publishes the user-selected radio MAC (or nil = auto) as :persistent_term (desired_adapter_key/0) before (re)starting the subtree — directly, or via the desired_adapter: opt on Bluez.start_link/1.
  2. Bluez.Client — already waiting for the daemon in its setup phase — matches that MAC against the Adapter1 objects and writes the resolved object path (adapter_path_key/0), falling back to the lowest-index adapter when the MAC is absent.

The path is then consistent for the lifetime of a subtree incarnation (crash-restarts re-resolve against the same desired MAC). The default — term never written, e.g. host tests or pre-setup — is /org/bluez/hci0.

Reading the term costs nanoseconds, so callers just call adapter_path/0 per use rather than caching it.

Summary

Functions

Object path of the BlueZ adapter all device paths hang off — wherever the host pointed the subtree, /org/bluez/hci0 by default.

The :persistent_term key the resolved adapter path is published under. Written by Bluez.Client at setup (and tests).

The selected radio MAC (nil = auto/first).

The :persistent_term key holding the user-selected radio MAC ("AA:BB:CC:DD:EE:FF" | nil = auto). Written by the host before each subtree start (or via the desired_adapter: opt); read via desired_adapter/0 during Bluez.Client setup.

Build the device object path for a packed MAC address.

Parse a device object path back into a packed MAC address.

Whether address is a representable 48-bit MAC. The wire type is uint64, so a hostile client can send values from_address/1 would refuse — validate before converting.

Functions

adapter_path()

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

Object path of the BlueZ adapter all device paths hang off — wherever the host pointed the subtree, /org/bluez/hci0 by default.

adapter_path_key()

@spec adapter_path_key() :: tuple()

The :persistent_term key the resolved adapter path is published under. Written by Bluez.Client at setup (and tests).

desired_adapter()

@spec desired_adapter() :: String.t() | nil

The selected radio MAC (nil = auto/first).

desired_adapter_key()

@spec desired_adapter_key() :: tuple()

The :persistent_term key holding the user-selected radio MAC ("AA:BB:CC:DD:EE:FF" | nil = auto). Written by the host before each subtree start (or via the desired_adapter: opt); read via desired_adapter/0 during Bluez.Client setup.

from_address(address)

@spec from_address(non_neg_integer()) :: String.t()

Build the device object path for a packed MAC address.

iex> Bluez.DevicePath.from_address(0xAABBCCDDEEFF)
"/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF"

to_address(path)

@spec to_address(String.t()) :: {:ok, non_neg_integer()} | :error

Parse a device object path back into a packed MAC address.

iex> Bluez.DevicePath.to_address("/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF") == {:ok, 0xAABBCCDDEEFF}
true

Returns :error for anything that isn't a device path under the current adapter (including child paths like .../dev_X/service000a).

valid?(address)

@spec valid?(term()) :: boolean()

Whether address is a representable 48-bit MAC. The wire type is uint64, so a hostile client can send values from_address/1 would refuse — validate before converting.