Skip to content

Instantly share code, notes, and snippets.

@pelwell
Created July 7, 2026 19:46
Show Gist options
  • Select an option

  • Save pelwell/baadd8eafbb48cf94d5897036f280be1 to your computer and use it in GitHub Desktop.

Select an option

Save pelwell/baadd8eafbb48cf94d5897036f280be1 to your computer and use it in GitHub Desktop.
Register a GATT peripheral advertisement with BlueZ over D-Bus
#!/usr/bin/env python3
"""
Register a GATT peripheral advertisement with BlueZ over D-Bus.
Uses org.bluez.LEAdvertisingManager1.RegisterAdvertisement to advertise
one 128-bit service UUID and a local name.
Requires: python3-dbus, python3-gi (PyGObject), and a running bluetoothd
with an adapter that supports LE advertising (hci0 by default).
Usage:
sudo python3 ble_adv_register.py [--uuid <128-bit-uuid>] [--name <local-name>] [--adapter hci0]
"""
import argparse
import sys
import dbus
import dbus.exceptions
import dbus.mainloop.glib
import dbus.service
from gi.repository import GLib
BLUEZ_SERVICE_NAME = "org.bluez"
DBUS_OM_IFACE = "org.freedesktop.DBus.ObjectManager"
DBUS_PROP_IFACE = "org.freedesktop.DBus.Properties"
LE_ADVERTISING_MANAGER_IFACE = "org.bluez.LEAdvertisingManager1"
LE_ADVERTISEMENT_IFACE = "org.bluez.LEAdvertisement1"
DEFAULT_UUID = "12345678-1234-5678-1234-56789abcdef0"
DEFAULT_NAME = "MyGattPeripheral"
class InvalidArgsException(dbus.exceptions.DBusException):
_dbus_error_name = "org.freedesktop.DBus.Error.InvalidArgs"
class Advertisement(dbus.service.Object):
"""Implements org.bluez.LEAdvertisement1 for a single peripheral advert."""
PATH_BASE = "/org/bluez/example/advertisement"
def __init__(self, bus, index, uuid, local_name):
self.path = f"{self.PATH_BASE}{index}"
self.bus = bus
self.ad_type = "peripheral"
self.service_uuids = [uuid]
self.local_name = local_name
self.include_tx_power = True
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
properties = {
"Type": self.ad_type,
"ServiceUUIDs": dbus.Array(self.service_uuids, signature="s"),
"LocalName": dbus.String(self.local_name),
"IncludeTxPower": dbus.Boolean(self.include_tx_power),
}
return {LE_ADVERTISEMENT_IFACE: properties}
def get_path(self):
return dbus.ObjectPath(self.path)
@dbus.service.method(DBUS_PROP_IFACE, in_signature="s", out_signature="a{sv}")
def GetAll(self, interface):
if interface != LE_ADVERTISEMENT_IFACE:
raise InvalidArgsException("Unsupported interface: " + interface)
return self.get_properties()[LE_ADVERTISEMENT_IFACE]
@dbus.service.method(LE_ADVERTISEMENT_IFACE, in_signature="", out_signature="")
def Release(self):
print(f"{self.path}: Released")
def find_adapter(bus, adapter_name):
"""Locate the adapter object path that exposes LEAdvertisingManager1."""
remote_om = dbus.Interface(
bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE
)
objects = remote_om.GetManagedObjects()
for path, interfaces in objects.items():
if LE_ADVERTISING_MANAGER_IFACE in interfaces:
if adapter_name is None or path.endswith(adapter_name):
return path
return None
def register_ad_cb():
print("Advertisement registered")
def register_ad_error_cb(error):
print(f"Failed to register advertisement: {error}", file=sys.stderr)
mainloop.quit()
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--uuid", default=DEFAULT_UUID, help="128-bit service UUID to advertise")
parser.add_argument("--name", default=DEFAULT_NAME, help="Local name to advertise")
parser.add_argument("--adapter", default="hci0", help="Adapter name (default: hci0)")
args = parser.parse_args()
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
adapter_path = find_adapter(bus, args.adapter)
if adapter_path is None:
print(
f"LEAdvertisingManager1 not found for adapter '{args.adapter}'. "
"Is bluetoothd running and does the adapter support LE advertising?",
file=sys.stderr,
)
sys.exit(1)
ad_manager = dbus.Interface(
bus.get_object(BLUEZ_SERVICE_NAME, adapter_path), LE_ADVERTISING_MANAGER_IFACE
)
advertisement = Advertisement(bus, 0, args.uuid, args.name)
global mainloop
mainloop = GLib.MainLoop()
ad_manager.RegisterAdvertisement(
advertisement.get_path(),
{},
reply_handler=register_ad_cb,
error_handler=register_ad_error_cb,
)
print(f"Advertising on {adapter_path} as '{args.name}' with UUID {args.uuid}")
print("Press Ctrl+C to stop and unregister.")
try:
mainloop.run()
except KeyboardInterrupt:
pass
finally:
try:
ad_manager.UnregisterAdvertisement(advertisement.get_path())
except dbus.exceptions.DBusException as e:
print(f"Error unregistering advertisement: {e}", file=sys.stderr)
advertisement.remove_from_connection()
print("Advertisement unregistered")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment