Build a GATT tree from BlueZ's GetManagedObjects reply.
Once a device's ServicesResolved flips true, its GATT database appears
as org.bluez.GattService1 / GattCharacteristic1 / GattDescriptor1
objects under the device path. This module turns those (rebus-decoded)
objects into:
Bluez.Gatt.Servicestructs, emitted to the host via{:gatt_service, address, service}events (the app-side translator reshapes them for Home Assistant), and- handle ↔ object-path maps for executing handle-keyed GATT requests.
Pure and host-testable — the D-Bus I/O stays in Bluez.Gatt.
Handle convention (bleak-compatible)
BlueZ encodes the ATT handle of each attribute in the object path's hex
suffix (service000a/char000b/desc000d). For characteristics that is the
declaration handle; clients address the value attribute, which always
sits at declaration + 1. We report path_handle + 1 for characteristics
(exactly what bleak — HA's own BlueZ backend — does), and the path handle
as-is for services and descriptors. HA echoes our reported handles back
in GATT requests, and by_handle is keyed by the same reported handles,
so the mapping is self-consistent — and consistent with HA's cached GATT
databases from bleak/ESP32 proxies, which also carry value handles.
Hierarchy
Children are attached via their parent object-path properties
(Characteristic.Service, Descriptor.Characteristic) rather than path
string prefixes. Objects with malformed paths (no parseable handle) or
dangling parent references are dropped.
Summary
Functions
Build the GATT tree for device_path from a GetManagedObjects object
list (rebus-decoded: [{path, [{iface, props_list}]}]).
Map a BlueZ Flags string list to the GATT properties bitmask.
Convert a BlueZ UUID string to the Bluez.Gatt.Service.uuid/0
shape: a 16-bit integer when it's a SIG base UUID (smaller on the wire,
matches what ESP32 proxies send), else the full 16-byte binary. Invalid
strings map to the all-zero UUID rather than crashing the tree build.
Types
@type kind() :: :characteristic | :descriptor
@type t() :: %Bluez.GattTree{ by_handle: %{required(non_neg_integer()) => {kind(), String.t()}}, handle_by_char_path: %{required(String.t()) => non_neg_integer()}, mtu: non_neg_integer() | nil, services: [Bluez.Gatt.Service.t()] }
Functions
Build the GATT tree for device_path from a GetManagedObjects object
list (rebus-decoded: [{path, [{iface, props_list}]}]).
mtu is the ATT MTU reported by any of the device's characteristics
(bluetoothd -E exposes the experimental MTU property), or nil when
none carries one.
@spec properties_mask([String.t()] | term()) :: non_neg_integer()
Map a BlueZ Flags string list to the GATT properties bitmask.
iex> Bluez.GattTree.properties_mask(["read", "notify"]) == 0x12
true
@spec to_uuid(String.t() | term()) :: Bluez.Gatt.Service.uuid()
Convert a BlueZ UUID string to the Bluez.Gatt.Service.uuid/0
shape: a 16-bit integer when it's a SIG base UUID (smaller on the wire,
matches what ESP32 proxies send), else the full 16-byte binary. Invalid
strings map to the all-zero UUID rather than crashing the tree build.