Improv.Advert (improv v0.1.0)

Copy Markdown View Source

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

adv_path()

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

Object path the LEAdvertisement1 object is exported at.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

device_suffix()

@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(path)

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

Introspection XML for the advertisement object. Pure.

register(server \\ __MODULE__)

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

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

set_state(server \\ __MODULE__, state)

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

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

start_link(opts \\ [])

unregister(server \\ __MODULE__)

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

Unregister the advertisement (idempotent).