# `Bluez.DevicePath`
[🔗](https://github.com/bbangert/bluez/blob/v0.1.0/lib/bluez/device_path.ex#L1)

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.

# `adapter_path`

```elixir
@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`

```elixir
@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`

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

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

# `desired_adapter_key`

```elixir
@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`

```elixir
@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`

```elixir
@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?`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
