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
@type advert() :: %{ address: non_neg_integer(), rss: integer(), address_type: 0 | 1, raw_data: binary() }
Functions
@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).
Build the on_advertisement/1 map from unwrapped BlueZ props, or :skip.