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.
Summary
Functions
Object path the LEAdvertisement1 object is exported at.
Build the LEAdvertisement1 property list. Pure.
Returns a specification to start this module under a supervisor.
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.
Introspection XML for the advertisement object. Pure.
Register the advertisement with BlueZ (idempotent; runs off-loop).
Update the advertised current-state (atom from Protocol).
Unregister the advertisement (idempotent).
Functions
@spec adv_path() :: String.t()
Object path the LEAdvertisement1 object is exported at.
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.
Returns a specification to start this module under a supervisor.
See Supervisor.
@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.
Introspection XML for the advertisement object. Pure.
@spec register(GenServer.server()) :: :ok
Register the advertisement with BlueZ (idempotent; runs off-loop).
@spec set_state(GenServer.server(), atom()) :: :ok
Update the advertised current-state (atom from Protocol).
@spec unregister(GenServer.server()) :: :ok
Unregister the advertisement (idempotent).