IanHarvey / bluepy

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

BLE disconnectes when writeCharacteristic() is called in the notify example #253

Open grahaminnovations opened 6 years ago

grahaminnovations commented 6 years ago

Hi, The code I used is based on the ble notify example. I have a separate function to deal with the GPIO button interrupt response. It will call the writeCharacteristic and send a short command to the peripheral device. The peripheral device will send back an ACK notification. Sometimes it cause the ble to disconnect. Any idea why is that?

from bluepy import btle
import time
import RPi.GPIO as GPIO  
GPIO.setmode(GPIO.BCM)  

GPIO.setup(5, GPIO.IN)

def notify_Write(data):
    global svc, p
    ch_write = svc.getCharacteristics()[0]
    p.writeCharacteristic(ch_write.valHandle, data)

def my_callback(channel):
    print("falling edge detected on %s" % channel)
    notify_Write(b'\x01')

class MyDelegate(btle.DefaultDelegate):
    def __init__(self):
        btle.DefaultDelegate.__init__(self)

    def handleNotification(self, cHandle, data):
        # ... perhaps check cHandle
        # ... process 'data'
        print(data)

GPIO.add_event_detect(5, GPIO.FALLING, callback=my_callback, bouncetime=300)

# Initialisation  -------

p = btle.Peripheral("ee:6c:c1:cf:fe:f9", "random")
p.setDelegate( MyDelegate() )

# Setup to turn notifications on, e.g.
svc = p.getServiceByUUID("47491010-1011-537e-4f6c-d104768a1001")
ch_notify = svc.getCharacteristics()[1]
p.writeCharacteristic(ch_notify.valHandle+1, b'\x01\x00', withResponse=True)

# Main loop --------

while True:
    if p.waitForNotifications(1.0):
        # handleNotification() was called
        continue

    print("Waiting...")
    # Perhaps do something else here
grahaminnovations commented 6 years ago

The error message:


Traceback (most recent call last):
  File "/home/pi/GI-bleNotify/demo.py", line 111, in <module>
    if p.waitForNotifications(1.0):
  File "/usr/local/lib/python3.4/dist-packages/bluepy/btle.py", line 516, in waitForNotifications
    resp = self._getResp(['ntfy','ind'], timeout)
  File "/usr/local/lib/python3.4/dist-packages/bluepy/btle.py", line 369, in _getResp
    resp = self._waitResp(wantType + ['ntfy', 'ind'], timeout)
  File "/usr/local/lib/python3.4/dist-packages/bluepy/btle.py", line 337, in _waitResp
    raise BTLEException(BTLEException.INTERNAL_ERROR, "Unexpected response (%s)" % respType)
bluepy.btle.BTLEException: Unexpected response (wr)```
Thomas-Dimos commented 6 years ago

I received the same error and as it looks when we are waiting for notifications and in my case another thread is going to write a value to a characteristic, a response is produced, as the error says ,and in the _waitResp() in the source code of btle.py which is called from waitForNotifications(),the last else is fired and produces this error.The thing is that in my case the write operation is always executed but then this error comes up.I don't know if it is too simple but maybe in the last else of this function if the response type is 'wr' then you can just skip it and wait again for the correct response instead of throwing an exception,but since we can't modify the code we hope it will be fixed

ronnymajani commented 5 years ago

If you're using multithreading or some setup where multiple BLE functions can be called in "parallel" then what can happen is that the write() function will send a write command, and then wait to receive the write response "wr". One possible problem is, if there's another thread that's also waiting for a response (like calling waitForNotifications()) then when that response 'wr' is received, it can mistakenly get passed to waitForNotifications instead, and that function isn't expecting such a response type and will throw an error. One way to workaround this is to call write() with the withResponse argument set to True (.write(value, withResponse=True) ), which will queue the write request instead so you get everything in the right order? (not exactly sure what's going on in this case, I didn't bother to go deeper than this).

If you instead update the code to "ignore" the 'wr' response, then you could potentially block your code indefinitely.

ronnymajani commented 5 years ago

Oh wait, no no ignore that I found the problem: int the btle.py the following code snippet has an indentation error replace:

def _getResp(self, wantType, timeout=None):
        if isinstance(wantType, list) is not True:
            wantType = [wantType]

        while True:
            resp = self._waitResp(wantType + ['ntfy', 'ind'], timeout)
            if resp is None:
                return None

            respType = resp['rsp'][0]
            if respType == 'ntfy' or respType == 'ind':
                hnd = resp['hnd'][0]
                data = resp['d'][0]
                if self.delegate is not None:
                    self.delegate.handleNotification(hnd, data)
                if respType not in wantType:  # [ERROR] indented one more level than it should be
                    continue  # [ERROR] this will never happen
            return resp

with:

def _getResp(self, wantType, timeout=None):
        if isinstance(wantType, list) is not True:
            wantType = [wantType]

        while True:
            resp = self._waitResp(wantType + ['ntfy', 'ind'], timeout)
            if resp is None:
                return None

            respType = resp['rsp'][0]
            if respType == 'ntfy' or respType == 'ind':
                hnd = resp['hnd'][0]
                data = resp['d'][0]
                if self.delegate is not None:
                    self.delegate.handleNotification(hnd, data)
            if respType not in wantType:
                continue
            return resp

the part that's suppose to continue the loop if the expected response isn't received yet, is mistakenly indented one more tab, making it a redundant statement that never gets executed. Just make that fix and all will be good. I'm gonna submit a pull request soon.

ronnymajani commented 5 years ago

Ok, well it seems that my first comment was more accurate, the issue still presists, and it definitely seems to be a multithreading (non linear code execution, in your case the use of interrupts) issue where the 'wr' response is propagated to waitForNotifications() instead of .write(). Like I said, just set withResponse to True and you should be fine. The only downside is a slowdown due to using write with response.