IanHarvey / bluepy

Python interface to Bluetooth LE on Linux
Other
1.6k stars 491 forks source link

Issue with bluepy, SPP profile, security level 3, connection comes up rarely #457

Open StephanGH opened 3 years ago

StephanGH commented 3 years ago

Hi there,

I used to connect a generic Bluetooth dongle (ORICO-BTA-508) for connection to a device under development. The device under development is a SPP profile device, so it just provides a simple serial interface. The bluepy script in use (developed with help of http://ianharvey.github.io/bluepy-doc/index.html) is straight and simple:

`

!/usr/bin/env python3

""" \file io.py Communication helper

SPP_SERVICE = "4880c12cfdcb40778920a450d7f9b907" SPP_DATA = "fec26ec46d7144429f8155bc21d658d6" DeviceName = btle.UUID(0x2A00) FirmwareRev = btle.UUID(0x2A26) Manufacturer = btle.UUID(0x2A29)

DEFAULT_BLE_CHUNK = 247 BLE_NOTIFICATION_TIMEOUT = 1.0 class BLEDongleDelegate(btle.DefaultDelegate):

def __init__(self, dongle):
    btle.DefaultDelegate.__init__(self)
    self.dongle = dongle
    self.dongle.result = bytearray( [] )
    self.dongle.timeout = BLE_NOTIFICATION_TIMEOUT
    print("init dongle: %f" % self.dongle.timeout)

def handleNotification(self, cHandle, data):
    #print "notification got"
    #self.dongle.result += bytearray(data)
    for x in bytearray(data):
        print("0x%02x" % x, end = " ")
    if len(data) == 244:    # this means the MTU (including 3 Bytes Overhead) is complete and
        self.dongle.device.waitForNotifications(self.dongle.timeout) # there might be more data
    else:
        print("") # end with line break

class myFL500C:

def getChar(self, concreteuuid):
    printname = concreteuuid.getCommonName()
    value = self.device.getCharacteristics(uuid=concreteuuid)[0].read()
    print("Characteristic: \"%s\" = %s, len:%i" % (printname, value, len(value)))

def __init__(self, bleAddress, debug=False):
    self.device = btle.Peripheral(bleAddress, addrType=btle.ADDR_TYPE_PUBLIC, iface=0)
    myobj = self.device.getCharacteristics()

    self.getChar(DeviceName)
    self.getChar(FirmwareRev)
    self.getChar(Manufacturer)
    time.sleep(1)
    self.device.setDelegate(BLEDongleDelegate(self))
    print("init Fl500C :%f" % self.timeout)
    self.device.setMTU(DEFAULT_BLE_CHUNK)
    self.service = self.device.getServiceByUUID(SPP_SERVICE)

    self.writeCharacteristic = self.service.getCharacteristics(forUUID=SPP_DATA)[0]
    #self.device.writeCharacteristic(self.writeCharacteristic.valHandle + 1, b"\x01") #, withResponse=True

    self.debug = debug
    self.opened = True

def exchange(self, apdu):
    if self.debug:
       print("=> %s" % hexlify(apdu))
    offset = 0
    self.result = ""
    while(offset < len(apdu)):
        #if self.debug:
        #    print("=> %s" % hexlify(apdu[offset:offset + DEFAULT_BLE_CHUNK]))
        data = apdu[offset:offset + DEFAULT_BLE_CHUNK]
        self.writeCharacteristic.write(data, withResponse=True)
        #print "written"
        offset += DEFAULT_BLE_CHUNK
    self.device.waitForNotifications(self.timeout)

def close(self):
    if self.opened:
        try:
            self.peripheral.disconnect()
        except:
            pass
    self.opened = False

if len(sys.argv) != 2: print("syntax: %s " % sys.argv[0]) sys.exit() else: MAC = sys.argv[1] print("connecting %s" % MAC) sys.argv = [sys.argv[0]]

dongle = myFL500C(MAC)

try: print("starting Stdin-Thread") print("DATA") for fileinput_line in fileinput.input(): if 'quit' == fileinput_line.rstrip(): break if 'timeout' == fileinput_line[:7]: cols=re.split("=", fileinput_line) dongle.timeout = float(cols[1]) continue byte_array = fileinput_line.rstrip() cols=re.split("x", byte_array) if len(cols) > 1: cols=cols[1:] myBytes = bytearray() for n in cols: myBytes += bytearray.fromhex(n)

    dongle.exchange( myBytes )

except KeyboardInterrupt:

User interrupt the program with ctrl+c

print("Done")
sys.exit()

finally: dongle.close() ` I run that script on Raspberry Pi 3 after I have verified the device is advertising using blescan.

But since i have upgraded to Python 3.7 (and after I've adapted that scripts syntax), the connection comes up in 1 of 5 tries, only. Before the upgrade bluepy was run with Python 2.7 and it failed only rarely to establish a connection, which was workarounded by at most 1 retry.

I found no reason yet (comparing logs of btmon, comparing a working with a failing attempt), except that it seems to struggle for timeouts and disconnects before the connection is secured.

Expected (and rarely seen outputs) of SPP_Client.py :

./SPP_Client 11:11:11:11:11:11 connecting 11:11:11:11:11:11 Characteristic: "Device Name" = b'FL5C-10000006\x00\x01\x00\x02\x01\x06', len:19 Characteristic: "Firmware Revision String" = b'1.0.1\x00\x00\x00\x00\x00\x00\x00\x00\x00', len:14 Characteristic: "Manufacturer Name String" = b'SICK ', len:12 init dongle: 1.000000 init Fl500C :1.000000 starting Stdin-Thread DATA x01x03x0CxB7x00x01x37x7C (user input, some telegram to send to the device) 0x01 0x03 0x02 0x03 0x41 0x78 0x84 (returned response from that device)

Usual error outputs of SPP_Client.py :

./SPP_Client 11:11:11:11:11:11 connecting 11:11:11:11:11:11 Traceback (most recent call last): File "./SPP_Client", line 105, in dongle = myFL500C(MAC) File "./SPP_Client", line 60, in init self.getChar(DeviceName) File "./SPP_Client", line 53, in getChar value = self.device.getCharacteristics(uuid=concreteuuid)[0].read() File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 508, in getCharacteristics rsp = self._getResp('find') 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

Sometimes it generates a bit more of output:

./SPP_Client 11:11:11:11:11:11 connecting 11:11:11:11:11:11 Traceback (most recent call last): File "./SPP_Client", line 105, in dongle = myFL500C(MAC) File "./SPP_Client", line 60, in init self.getChar(DeviceName) File "./SPP_Client", line 53, in getChar value = self.device.getCharacteristics(uuid=concreteuuid)[0].read() File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 508, in getCharacteristics rsp = self._getResp('find') 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 361, in _waitResp self._stopHelper() File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 293, in _stopHelper self._helper.stdin.flush() BrokenPipeError: [Errno 32] Broken pipe Exception ignored in: <function Peripheral.del at 0x76620780> Traceback (most recent call last): File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 630, in del self.disconnect() File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 453, in disconnect self._writeCmd("disc\n") File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 305, in _writeCmd self._helper.stdin.flush() BrokenPipeError: [Errno 32] Broken pipe

Version infos:

pip list


bluepy 1.3.0 pip 20.3.4 RPi.GPIO 0.7.0

Bonding was accomplished using the interactive bluetoothctl. Some other information you might be interested in:

lsb_release -C Codename: buster uname -r 5.10.60-v7+

The device under development is stable meanwhile, connections using a mobile phone or an android tablet do work very well.

Any help or hint with this would be appreciated.