Search code examples
pythondbusbluezgatt

Connect to a Bluetooth LE device using bluez python dbus interface


I would like to connect to a Bluetooth LE device and receive notifications from it in python. I would like to use the Bluez dbus API, but can't find an example I can understand. :-)

With gatttool, I can use the following command:

gatttool -b C4:8D:EE:C8:D2:D8 --char-write-req -a 0x001d -n 0100 –listen

How can I do the same in python, using the dbus API of Bluez?


Solution

  • To use the BlueZ DBus API's to be a gatt client is probably easiest with the pydbus library. https://pypi.org/project/pydbus/

    The BlueZ DBus API documents are available at:

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt

    Some useful things to know to get you started:

    1. The Dbus service for bluez is called 'org.bluez'
    2. The Bluetooth adapter on a Raspberry Pi normally has '/org/bluez/hci0' as DBus object path.
    3. The DBus Object path to a device is the adapter path plus the mac address prepended by 'dev_' and the semi-colons replaced with underscores. i.e. 'DE:82:35:E7:43:BE' would be found at '/org/bluez/hci0/dev_DE_82_35_E7_43_BE'

    I don't have your device so I have done an example using a BBC micro:bit to change the value of how often it updates the temperature value (period) and then get notifications of those temperature values. Hopefully it will be easy to adapt this to your situation.

    This code does assume that you have already used bluetoothctl to pair your device. Type bluetoothctl devices on the command line and if you device is in the list, then this is a good sign.

    import pydbus
    from gi.repository import GLib
    
    # Setup of device specific values
    dev_id = 'DE:82:35:E7:43:BE'
    adapter_path = '/org/bluez/hci0'
    device_path = f"{adapter_path}/dev_{dev_id.replace(':', '_')}"
    temp_reading_uuid = 'e95d9250-251d-470a-a062-fa1922dfa9a8'
    temp_period_uuid = 'e95d1b25-251d-470a-a062-fa1922dfa9a8'
    
    # Setup DBus informaton for adapter and remote device
    bus = pydbus.SystemBus()
    mngr = bus.get('org.bluez', '/')
    adapter = bus.get('org.bluez', adapter_path)
    device = bus.get('org.bluez', device_path)
    # Connect to device (needs to have already been paired via bluetoothctl)
    device.Connect()
    
    # Some helper functions
    def get_characteristic_path(device_path, uuid):
        """Find DBus path for UUID on a device"""
        mng_objs = mngr.GetManagedObjects()
        for path in mng_objs:
            chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
            if path.startswith(device_path) and chr_uuid == uuid:
               return path
    
    def as_int(value):
        """Create integer from bytes"""
        return int.from_bytes(value, byteorder='little')
    
    # Get a couple of characteristics on the device we are connected to
    temp_reading_path = get_characteristic_path(device._path, temp_reading_uuid)
    temp_period_path = get_characteristic_path(device._path, temp_period_uuid)
    temp = bus.get('org.bluez', temp_reading_path)
    period = bus.get('org.bluez', temp_period_path)
    # Read value of characteristics
    print(temp.ReadValue({}))
    # [0]
    print(period.ReadValue({}))
    # [232, 3]
    print(as_int(period.ReadValue({})))
    # 1000
    
    # Write a new value to one of the characteristics
    new_value = int(1500).to_bytes(2, byteorder='little')
    period.WriteValue(new_value, {})
    
    # Enable eventloop for notifications
    def temp_handler(iface, prop_changed, prop_removed):
        """Notify event handler for temperature"""
        if 'Value' in prop_changed:
            print(f"Temp value: {as_int(prop_changed['Value'])} \u00B0C")
    
    mainloop = GLib.MainLoop()
    temp.onPropertiesChanged = temp_handler
    temp.StartNotify()
    try:
        mainloop.run()
    except KeyboardInterrupt:
        mainloop.quit()
        temp.StopNotify()
        device.Disconnect()