Bluez.GattTree (bluez v0.1.0)

Copy Markdown View Source

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.Service structs, 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

kind()

@type kind() :: :characteristic | :descriptor

t()

@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(objects, device_path)

@spec build([{String.t(), list()}], String.t()) :: t()

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.

properties_mask(flags)

@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

to_uuid(uuid)

@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.