Bluez.Advert (bluez v0.1.0)

Copy Markdown View Source

Reconstructs a BLE advertisement (the verbatim-ish AD byte structure plus the fields an ESPHome-style scanner host needs) from BlueZ's parsed device properties.

D-Bus / org.bluez does NOT expose the raw over-the-air AD bytes, only parsed properties (ManufacturerData, ServiceData, ServiceUUIDs, Name, TxPower, RSSI, Address). We re-serialize those back into AD elements. This is lossy — it drops AD element order, the Flags (0x01) element, unknown/proprietary AD types, and the adv/scan-response split — but it is faithful for the manufacturer- and service-data elements that Home Assistant's sensor decoders use (BTHome 0xFCD2, Govee, Xiaomi).

This is how HA's own Linux/BlueZ adapters reconstruct adverts (bluetooth-data-tools).

Inputs

reconstruct/1 takes a props map keyed by BlueZ property name with values already unwrapped from their D-Bus variants by Bluez.Client:

%{
  "Address" => "AA:BB:CC:DD:EE:FF",
  "AddressType" => "public" | "random",
  "RSSI" => -60,                       # int16, may be absent
  "ManufacturerData" => %{0x004C => <<...>>},   # company_id => bytes
  "ServiceData" => %{"0000fcd2-..." => <<...>>},# uuid string => bytes
  "ServiceUUIDs" => ["0000fcd2-0000-1000-8000-00805f9b34fb", ...],
  "Name" => "Govee_...",
  "TxPower" => -4
}

Output

{:ok, advert} where advert is the map shape Bluez.Client's on_advertisement: fun consumes, or :skip when there is no Address (can't address the advert) — RSSI/data may legitimately be absent on a given update.

Summary

Functions

Parse "AA:BB:CC:DD:EE:FF" into the MSB-first integer Home Assistant expects (0xAABBCCDDEEFF). Matches what blue_heron forwarded (validated on rpi3 against HA — no byte swap).

Build the on_advertisement/1 map from unwrapped BlueZ props, or :skip.

Types

advert()

@type advert() :: %{
  address: non_neg_integer(),
  rss: integer(),
  address_type: 0 | 1,
  raw_data: binary()
}

Functions

address_to_integer(mac)

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

Parse "AA:BB:CC:DD:EE:FF" into the MSB-first integer Home Assistant expects (0xAABBCCDDEEFF). Matches what blue_heron forwarded (validated on rpi3 against HA — no byte swap).

reconstruct(props)

@spec reconstruct(map()) :: {:ok, advert()} | :skip

Build the on_advertisement/1 map from unwrapped BlueZ props, or :skip.