128-bit (or long) UUID is always the one that is actually used internally. If the GATT Service or GATT characteristic has been adopted by the Bluetooth SIG, then it can be written in the shortened 16-bit form. The base 128-bit number is: 0000xxxx-0000-1000-8000-00805F9B34FB
and the x's get replaced with the 16-bit value.
Looking through the govee_btled, it seems the 128-bit UUID is:
UUID_CONTROL_CHARACTERISTIC = '00010203-0405-0607-0809-0a0b0c0d2b11'
I would be cautious of using either of the Bluetooth libraries you have linked to.
pygatt
, as you say, uses the command line tool gatttool which was deprecated by in 2017. This is discussed in the library https://github.com/peplin/pygatt/issues/112
pygattlib
uses HCI commands directly bypassing the Bluetooth Daemon running on your system. This is routinely done in the embedded space but considered bad practice on a general purpose system running Linux.
The recommended API for interacting with BlueZ is the D-Bus API and there is a pure Python implementation with pydbus
Looking at the bluetooth_led.py file, it doesn't seem like there are many commands that would need to match. I made it:
self._bt = bt_backend_cls()
self._bt.start()
self._bt.stop()
self._dev = self._bt.connect(self.mac)
self._dev.char_write(UUID_CONTROL_CHARACTERISTIC, frame)
self._dev.disconnect()
My thought would be that you could change the backend quite easily by importing a file like below (instead of import pygatt) and specifying bt_backend_cls
to be PyDbusBackend
class in that file. Apart from those two changes you shouldn't have to do any other changes.
from time import sleep
from pydbus import SystemBus
BLUEZ_SERVICE = 'org.bluez'
BLUEZ_DEV_IFACE = 'org.bluez.Device1'
BLUEZ_CHR_IFACE = 'org.bluez.GattCharacteristic1'
class PyDbusBackend:
@classmethod
def start(cls):
"""Mock function to match pygatt lib"""
pass
@classmethod
def stop(cls):
"""Mock function to match pygatt lib"""
pass
@classmethod
def connect(cls, device_address):
"""Create device object and connect"""
device = PyDbusDevice(device_address)
device.connect()
return device
class PyDbusDevice:
def __init__(self, address):
self.bus = SystemBus()
self.mngr = self.bus.get(BLUEZ_SERVICE, '/')
self.dev_path = self._from_device_address(address)
self.device = self.bus.get(BLUEZ_SERVICE, self.dev_path)
def _from_device_address(self, addr):
"""Look up D-Bus object path from device address"""
mng_objs = self.mngr.GetManagedObjects()
for path in mng_objs:
dev_addr = mng_objs[path].get(BLUEZ_DEV_IFACE, {}).get('Address', '')
if addr.casefold() == dev_addr.casefold():
return path
def _from_gatt_uuid(self, uuid):
"""Look up D-Bus object path for characteristic UUID"""
mng_objs = self.mngr.GetManagedObjects()
for path in mng_objs:
chr_uuid = mng_objs[path].get(BLUEZ_CHR_IFACE, {}).get('UUID')
if path.startswith(self.dev_path) and chr_uuid == uuid.casefold():
return path
def connect(self):
"""
Connect to device.
Wait for GATT services to be resolved before returning
"""
self.device.Connect()
while not self.device.ServicesResolved:
sleep(0.5)
def disconnect(self):
"""Disconnect from device"""
self.device.Disconnect()
def char_write(self, uuid, value):
"""Write value to given GATT characteristic UUID"""
char_path = self._from_gatt_uuid(uuid)
char = self.bus.get(BLUEZ_SERVICE, char_path)
char.WriteValue(value, {})
def char_read(self, uuid):
"""Read value of given GATT characteristic UUID"""
char_path = self._from_gatt_uuid(uuid)
char = self.bus.get(BLUEZ_SERVICE, char_path)
return char.ReadValue({})
Changing to this backend should then allow you to connect to multiple lights.