b3b / able

Python for Android Bluetooth Low Energy package
MIT License
38 stars 18 forks source link

Gatt must be closed on disconnect. If not, connectGatt won't reconnect. #33

Closed robgar2001 closed 2 years ago

robgar2001 commented 2 years ago

ABLE was not able to reconnect when walking out of range.

This was due to the connectGatt method. After walking out of range, the mBluetoothGatt object was not equal to null. Thereby blocking the ability to reconnect. When walking of out range, the onConnectionStateChanged method is called. In my case with state disconnected en status 8. To fix the connectGatt problem, it is sufficient to close the gatt connection when onConnectionStateChanged is called with state argument STATE_DISCONNECTED.

b3b commented 2 years ago

Thanks @robgar2001 Seems like a reasonable fix. I will check disconnecting scenarios on my devices.

b3b commented 2 years ago

I like the idea to simplify the reconnection management, but closing GATT and restart scan is not always desired approach. It is possible to try to reconnect to device using existing BluetoothGatt object: https://developer.android.com/reference/android/bluetooth/BluetoothGatt#connect()

For example:

from kivy.app import App
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.uix.widget import Widget

from able import BluetoothDispatcher, GATT_SUCCESS

class BLESender(BluetoothDispatcher):

    def __init__(self):
        super().__init__()
        self._connected = False
        Clock.schedule_once(self.restart_scan_if_needed, 0)

    def on_device(self, device, rssi, advertisement):
        if device.getName() == "KivyBLETest":
            self.device = device
            self.stop_scan()

    def on_scan_completed(self):
        if self.device:
            self.connect_gatt(self.device)

    def on_connection_state_change(self, status, state):
        Logger.info("Connection state changed <%d> <%d>", status, state)
        if state and status == GATT_SUCCESS:
            self._connected = True
        else:
            self._connected = False
            Logging.info("Initiate reconnection")
            self.gatt.connect()
            # After 20 seconds check if reconnection was successful
            Clock.schedule_once(self.restart_scan_if_needed, 20)

    def restart_scan_if_needed(self, *args, **kwargs):
        if not self._connected:
            Logger.info("Start scan")
            self.device = None
            self.close_gatt()  # Close the connection,
            self.start_scan()  # and start scan again

class ExampleApp(App):

    def build(self):
        BLESender()
        return Widget()

if __name__ == '__main__':
    ExampleApp().run()

@robgar2001 what do you think, maybe this approach can be used in your application?

robgar2001 commented 2 years ago

HI @b3b

Thanks for replying! I see what you mean.

Calling BluetoothGatt.connect() or closeGatt() in the disconnected state still feels a bit odd. Since able is meant as an interface to the android ble api, sticking to the android.developer docs seems the right solution.

Maybe a slight logging improvement to inform people would do wonders?

https://github.com/robgar2001/able/blob/f5772b441cfe0ca472f7edc63052c5ea3b41fda1/able/src/org/able/BLE.java#L162

b3b commented 2 years ago

@robgar2001 great, it can help users to debug if issue is happen. I open #34 for possible steps to document and mitigate re-connection error.