bluetoothraspberry-pibluetooth-lowenergyraspbianesp32

BluePy Frequent BLE Disconnects between Raspberry Pi4 and ESP32 - (Bluetooth)


I (like others) have multiple disconnects between a RPi4 central(client) and ESP32 BLE peripheral(server). Using the "nRF Connect" app on a android phone, the connection to the ESP32 is robust. However, RPi4 - ESP32 BLE communication is VERY unstable. This finding implies the fault is with the RPi and/or code. The initial BLE connection occurs faithfully but the connection inevitably drops after a random number of successful reads (usually 1-50 reads). I am using BluePy 1.3.0 with a new Raspbian image on a RPI4. I attached skeleton code and the error message produced after a random number of successful Reads.

    import time
    from bluepy.btle import Peripheral

    peripheral_address = "8c:aa:b5:85:20:1e"
    service_uuid =  "537e7010-9928-4595-89dc-46b495862dc6"
    characteristic_uuid = "3778ceab-0974-4eb0-9da5-26c3a69cc742" # Read from peripheral

    p = Peripheral(peripheral_address, "public") #random does not work!!
    Service=p.getServiceByUUID(service_uuid)
    Characterization=Service.getCharacteristics(characteristic_uuid)[0]
    print("Got characterization")

    time.sleep(1)

    while True:
        value = Characterization.read()
        print(value)
        time.sleep(0.1)

Traceback (most recent call last):
  File "/home/pi/Desktop/BLETest/bleRead.py", line 16, in <module>
    value = Characterization.read()
  File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 197, in read
    return self.peripheral.readCharacteristic(self.valHandle)
  File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 530, in readCharacteristic
    resp = self._getResp('rd')
  File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 407, in _getResp
    resp = self._waitResp(wantType + ['ntfy', 'ind'], timeout)
  File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 362, in _waitResp
    raise BTLEDisconnectError("Device disconnected", resp)
bluepy.btle.BTLEDisconnectError: Device disconnected

Note: Adding code to catch the disconnect exception has been unsuccessful thus far resulting in additional raised errors and loss of data secondary to the time it takes to reconnect.

I am very interested to hear from anyone who has robust BLE communication with a RPi client? Any and all help is appreciated. Thank you.


Solution

  • There are a large number of things it could be but there a couple areas that I would investigate.

    Firstly there is a bit churn going on with Bluetooth firmware on the RPi at the moment as you can see from this thread: https://github.com/RPi-Distro/firmware-nonfree/issues/8

    So I would check that you are up to date with with those.

    BluePy I believe has a bluepy-helper module which is based on Bluez version 5.47 which is behind what the RPi is using now. Might be worth trying a different library to see if the issue persists.

    Here is an example of reading your characteristic with the BlueZ D-Bus API directly using pydbus for the python D-Bus bindings:

    from time import sleep
    import pydbus
    from gi.repository import GLib
    
    peripheral_address = "8C:AA:B5:85:20:1E"
    service_uuid =  "537e7010-9928-4595-89dc-46b495862dc6"
    characteristic_uuid = "3778ceab-0974-4eb0-9da5-26c3a69cc742" # Read from peripheral
    
    # DBus object paths
    BLUEZ_SERVICE = 'org.bluez'
    ADAPTER_PATH = '/org/bluez/hci0'
    device_path = f"{ADAPTER_PATH}/dev_{peripheral_address.replace(':', '_')}"
    
    # setup dbus
    bus = pydbus.SystemBus()
    mngr = bus.get(BLUEZ_SERVICE, '/')
    adapter = bus.get(BLUEZ_SERVICE, ADAPTER_PATH) 
    device = bus.get(BLUEZ_SERVICE, device_path)
    
    device.Connect()
    
    while not device.ServicesResolved:
        sleep(0.5)
    
    def get_characteristic_path(dev_path, uuid):
        """Look up DBus path for characteristic UUID"""
        mng_objs = mngr.GetManagedObjects()
        for path in mng_objs:
            chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
            if path.startswith(dev_path) and chr_uuid == uuid.casefold():
               return path
    
    # Characteristic DBus information
    char_path = get_characteristic_path(device._path, characteristic_uuid)
    characterization = bus.get(BLUEZ_SERVICE, char_path)
    
    # Read characteristic without event loop notifications
    while True:
        print(characterization.ReadValue({}))
        time.sleep(0.1)
    

    Does the characteristic you are reading from support notifications? If it does then that is a more efficient way to use the Bluetooth link. The above while loop can be replaced with:

    # Enable eventloop for notifications
    def notify_handler(iface, prop_changed, prop_removed):
        """Notify event handler for characteristic"""
        if 'Value' in prop_changed:
            new_value = prop_changed['Value']
            print(f"Received: {new_value}")
    
    
    mainloop = GLib.MainLoop()
    characterization.onPropertiesChanged = notify_handler
    characterization.StartNotify()
    try:
        mainloop.run()
    except KeyboardInterrupt:
        mainloop.quit()
        characterization.StopNotify()
        device.Disconnect()