# `Improv.Advert`
[🔗](https://github.com/bbangert/improv/blob/v0.1.1/lib/improv/advert.ex#L1)

Exports an `org.bluez.LEAdvertisement1` object and registers it with the
adapter's `org.bluez.LEAdvertisingManager1`, so the device advertises the
Improv service while provisioning is armed.

Same export+dispatch shape as `Bluez.Agent` (own `rebus`
connection, `set_method_handler`, reply to inbound calls, register off-loop in
a `Task`). The advertisement is a *single* read-only object with a `Release()`
method BlueZ calls when it drops the advert.

## What we advertise

  * `Type` = `"peripheral"` (connectable),
  * `ServiceUUIDs` = `[improv service uuid]` (128-bit),
  * `ServiceData` = the spec's 6-byte payload keyed by the 16-bit `"4677"`
    UUID (see below),
  * `LocalName` = the device name (falls to the scan-response packet),
  * `Discoverable` = true.

## ServiceData and the 31-byte legacy-advertising budget

The 128-bit service UUID consumes 18 of the 31-byte legacy budget, and the
rpi3 controller (BCM4345C0, BT 4.x) has no LE Extended Advertising. A June
2026 attempt keyed ServiceData by the **128-bit** UUID (~24-byte AD) made
`bluetoothd` reject the advert with "Invalid Parameters" (HW-found on rpi3).
The spec's form — 6-byte payload `[state, capabilities, 0 ×4]` keyed by the
**16-bit** `"4677"` UUID — totals exactly 31 bytes alongside Flags + the
128-bit ServiceUUIDs, so it fits. It is **static at registration** (state
frozen at `:authorized`): BlueZ reads LEAdvertisement1 properties once at
RegisterAdvertisement, dynamic updates would need re-registration churn,
and legacy controllers stop advertising while a client is connected — which
is when all post-`:authorized` states occur. Clients read live state and
capabilities from the GATT characteristics after connecting (improv-wifi.com
and the HA app both do), so nothing is lost. `set_state/2` is therefore a
no-op, kept only so the manager's transitions stay uniform.

# `adv_path`

```elixir
@spec adv_path() :: String.t()
```

Object path the LEAdvertisement1 object is exported at.

# `advertisement_props`

```elixir
@spec advertisement_props(String.t(), binary()) :: list()
```

Build the `LEAdvertisement1` property list. Pure.

`ServiceData` carries the spec's 6-byte payload `[current_state,
capabilities, 0 ×4]` keyed by the 16-bit `"4677"` UUID — the only form that
fits the 31-byte legacy budget next to the 128-bit ServiceUUIDs (see the
moduledoc for the June 128-bit-keyed rejection). The state byte is frozen
at `:authorized`; `capabilities` MUST be the same derived byte the GATT
capabilities characteristic serves (the supervisor derives it once).
`LocalName` falls to the scan-response packet.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `device_suffix`

```elixir
@spec device_suffix() :: String.t()
```

The short device-unique suffix used in the default local name: last 4 hex
of the device MAC, falling back to the hostname tail. Public so hosts can
build other device-facing strings (e.g. Device Info's device name) that
match the advertised BLE name.

# `introspect_xml`

```elixir
@spec introspect_xml(String.t()) :: String.t()
```

Introspection XML for the advertisement object. Pure.

# `register`

```elixir
@spec register(GenServer.server()) :: :ok
```

Register the advertisement with BlueZ (idempotent; runs off-loop).

# `set_state`

```elixir
@spec set_state(GenServer.server(), atom()) :: :ok
```

Update the advertised current-state (atom from `Protocol`).

# `start_link`

# `unregister`

```elixir
@spec unregister(GenServer.server()) :: :ok
```

Unregister the advertisement (idempotent).

---

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