bleak: Android backend does not give each service/characteristic a unique "handle".

  • bleak version: 0.14.3
  • Python version: Python 3.7.13 (Colab)
  • Operating System: Samsung SM-G900F Android 6.0.1, API 23

Description

Hello! I am trying to implement an application on an android mobile. This app needs to connect via low-energy bluetooth BLE to a scale (Mi Smart Scale 2) and take the weight. To create the apk of the application I use Colab. The problem is that when I launch the application it crashes immediately after starting and I cannot read the weight.

What I Did

import asyncio

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty

from bleak import BleakClient


kv = '''
WindowManager:
    
    MainWindow:
    BluetoothWindow:
    LoginWindow:
    
        
     
    
<LoginWindow>:
    name: "login"

    email: email
    password: password

    FloatLayout:

        Label:
            text:"Email: "
            font_size: (root.width**2 + root.height**2) / 13**4
            pos_hint: {"x":0.1, "top":0.9}
            size_hint: 0.35, 0.15

        TextInput:
            id: email
            font_size: (root.width**2 + root.height**2) / 13**4
            multiline: False
            pos_hint: {"x": 0.45 , "top":0.9}
            size_hint: 0.4, 0.15

        Label:
            text:"Password: "
            font_size: (root.width**2 + root.height**2) / 13**4
            pos_hint: {"x":0.1, "top":0.7}
            size_hint: 0.35, 0.15

        TextInput:
            id: password
            font_size: (root.width**2 + root.height**2) / 13**4
            multiline: False
            password: True
            pos_hint: {"x": 0.45, "top":0.7}
            size_hint: 0.4, 0.15

        Button:
            pos_hint:{"x":0.2,"y":0.05}
            size_hint: 0.6, 0.2
            font_size: (root.width**2 + root.height**2) / 13**4
            text: "Login"
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "left"

        Button:
            pos_hint:{"x":0.2,"y":0.3}
            size_hint: 0.6, 0.1
            font_size: (root.width**2 + root.height**2) / 17**4
            text: "Non hai un account? Creane uno"  
            on_release:
     
                
     
        
     
<MainWindow>:
    name: "main"
    BoxLayout:
		orientation: "vertical"
		size: root.width, root.height
        
    FloatLayout:
        n: n
        email: email
        created:created

        FloatLayout:
            Label:
                id: n
                pos_hint:{"x": 0.1, "top":0.9}
                size_hint:0.8, 0.2
                text: "Nome account: "

            Label:
                id: email
                pos_hint:{"x": 0.1, "top":0.7}
                size_hint:0.8, 0.2
                text: "Email: "

            Label:
                id: created
                pos_hint:{"x": 0.1, "top":0.5}
                size_hint:0.8, 0.2
                text: "Creato il: "

            Button:
                pos_hint:{"x":0.35, "y": 0.2}
                size_hint:0.3,0.1
                text: "Log Out"
                on_release:
                    app.root.current = "login"
                    root.manager.transition.direction = "right"
                

            Button:
                pos_hint:{"x":0.35, "y": 0.1}
                size_hint:0.3,0.1
                text: "Log In"
                on_release:
                    app.root.current = "ble"
                    root.manager.transition.direction = "left"
                
<BluetoothWindow>:
    name: "ble"
    peso: peso

    
    BoxLayout:
		orientation: "vertical"
		size: root.width, root.height

    FloatLayout:
        Label:
            id: peso
            pos_hint:{"x": 0.1, "top":0.7}
            size_hint:0.8, 0.2
            text: "peso: "
        
        Button:
            pos_hint:{"x":0.2, "y": 0.1}
            size_hint:0.6,0.2
            text: "Log Out"
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "right"
'''

class LoginWindow(Screen):
    pass

class MainWindow(Screen):
    pass

class BluetoothWindow(Screen):
    peso = ObjectProperty(None)
    
    def on_enter(self):

        self.peso.text = "peso: " + str(data2)


class WindowManager(ScreenManager):
    pass

sm = WindowManager()

screens = [MainWindow(name="main"), 
           BluetoothWindow(name="ble"),
           LoginWindow(name="login")]

for screen in screens:
    sm.add_widget(screen)


sm.current = "main"

class AsyncApp(App):

    other_task = None

    def build(self):
        return Builder.load_string(kv)
    
    def build2(self):
        return sm

    def app_func(self):
        '''This will run both methods asynchronously and then block until they
        are finished
        '''
        self.other_task = asyncio.ensure_future(self.get_the_weight_from_the_mi_smart_scale_2())

        async def run_wrapper():
            # we don't actually need to set asyncio as the lib because it is
            # the default, but it doesn't hurt to be explicit
            await self.async_run(async_lib='asyncio')
            print('App done')
            self.other_task.cancel()

        return asyncio.gather(run_wrapper(), self.other_task)
   
    global notification_handler
    def notification_handler(sender, data):
        """Simple notification handler which prints the data received."""
        data1 = list(data)
        global data2
        data2 = ((data1[1]+data1[2]*256)*0.005)
        print("{0}: {1}".format(sender, data2))

    async def get_the_weight_from_the_mi_smart_scale_2(self):
        #da modificare
        '''This method is also run by the asyncio loop and periodically prints
        something.
        '''
        async with BleakClient("70:87:9e:0f:f8:7d") as client:
            print(f"Connected: {client.is_connected}")
            
            await client.start_notify("00002a9d-0000-1000-8000-00805f9b34fb", notification_handler)
            await asyncio.sleep(30.0)
            await client.stop_notify("00002a9d-0000-1000-8000-00805f9b34fb")   
            print(f"Connected: {client.is_connected}")

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(AsyncApp().app_func())
    loop.close()
### What I read on Android Studio's log######################################
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 4
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2477/? W/bt_btif: bta_le_client_conn_param_cback, status : 0
2022-07-13 15:53:26.187 1983-2477/? W/bt_btif: bta_le_client_conn_param_cback, clcb found and client_if : 10
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2204/? D/BtGatt.GattService: onClientConnParamsChanged() - clientIf=10 address=70:87:9E:0F:F8:7D, interval=39status=0
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 3
2022-07-13 15:53:26.187 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 4
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 14122-14134/org.test.tesi4 D/BluetoothGatt: onClientConnParamsChanged() - Device=70:87:9E:0F:F8:7D interval=39 status=0
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.197 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 5
2022-07-13 15:53:26.207 1983-2415/? D/BtGatt.GattService: [gattSrvHandler] msg : 2
2022-07-13 15:53:26.207 14122-14134/org.test.tesi4 D/BluetoothGatt: onSearchComplete() = Device=70:87:9E:0F:F8:7D Status=0
2022-07-13 15:53:26.277 1983-2493/? D/bt_upio: ..proc_btwrite_timeout..
2022-07-13 15:53:26.277 1983-2493/? D/bt_upio: upio_set : pio 0 action 1, polarity 1
2022-07-13 15:53:26.357 14122-14150/org.test.tesi4 I/python: Connected: True
2022-07-13 15:53:26.357 14122-14150/org.test.tesi4 D/BluetoothGatt: cancelOpen() - device: 70:87:9E:0F:F8:7D
2022-07-13 15:53:26.367 1983-2015/? D/BtGatt.GattService: clientDisconnect() - address=70:87:9E:0F:F8:7D, connId=10
2022-07-13 15:53:26.367 1983-2477/? E/bt_btm: btm_ble_get_search_if search_if=4
2022-07-13 15:53:26.367 1983-2417/? D/bt_vendor: op for 7
2022-07-13 15:53:26.367 836-2532/? V/AlarmManager:  remove PendingIntent] PendingIntent{ec8bcc: PendingIntentRecord{83cd4dc com.android.bluetooth broadcastIntent}}
2022-07-13 15:53:26.367 1983-2417/? D/bt_upio: upio_set : pio 0 action 2, polarity 1
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=4 connected=0 conn_id=4 reason=0x0016
2022-07-13 15:53:26.367 1983-2417/? D/bt_upio: upio_start_stop_timer : timer_settime success
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=5 connected=0 conn_id=5 reason=0x0016
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=6 connected=0 conn_id=6 reason=0x0016
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=7 connected=0 conn_id=7 reason=0x0016
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=8 connected=0 conn_id=8 reason=0x0016
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=9 connected=0 conn_id=9 reason=0x0016
2022-07-13 15:53:26.367 1983-2417/? D/bt_upio: upio_set: proc btwrite assertion, buffer: 1, timer_armed 1 1
2022-07-13 15:53:26.367 1983-2477/? W/bt_btif: bta_gattc_conn_cback() - cif=10 connected=0 conn_id=10 reason=0x0016
2022-07-13 15:53:26.367 1983-2477/? E/bt_btif: bta_gattc_mark_bg_conn unable to find the bg connection mask for: 70:87:9e:0f:f8:7d
2022-07-13 15:53:26.367 1983-2204/? D/BtGatt.GattService: onDisconnected() - clientIf=10, connId=10, address=70:87:9E:0F:F8:7D
2022-07-13 15:53:26.367 14122-14157/org.test.tesi4 D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=10 device=70:87:9E:0F:F8:7D
2022-07-13 15:53:26.367 14122-14150/org.test.tesi4 D/BluetoothGatt: close()
2022-07-13 15:53:26.367 14122-14150/org.test.tesi4 D/BluetoothGatt: unregisterApp() - mClientIf=10
2022-07-13 15:53:26.377 1983-2505/? D/BtGatt.GattService: unregisterClient() - clientIf=10
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:  Traceback (most recent call last):
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:    File "/content/.buildozer/android/app/main.py", line 228, in <module>
2022-07-13 15:53:26.377 836-1510/? V/AlarmManager:  remove PendingIntent] PendingIntent{317de15: PendingIntentRecord{83cd4dc com.android.bluetooth broadcastIntent}}
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:    File "/content/.buildozer/android/platform/build-armeabi-v7a/build/other_builds/python3/armeabi-v7a__ndk_target_21/python3/Lib/asyncio/base_events.py", line 616, in run_until_complete
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:    File "/content/.buildozer/android/app/main.py", line 216, in get_the_weight_from_the_mi_smart_scale_2
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:    File "/content/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/tesi4/armeabi-v7a/bleak/backends/p4android/client.py", line 486, in start_notify
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python:  bleak.exc.BleakError: Characteristic with UUID 00002a9d-0000-1000-8000-00805f9b34fb could not be found!
2022-07-13 15:53:26.377 14122-14150/org.test.tesi4 I/python: Python for android ended.
2022-07-13 15:53:26.387 1983-2477/? I/bt_btm_sec: btm_sec_disconnected clearing pending flag handle:64 reason:22

About this issue

Most upvoted comments

The bug in Bleak needs to be fixed. All characteristics in the dictionary are using the same key which writes over the previous key, so only the last discovered characteristic can be used currently.

If Android doesn’t see the characteristic at all, e.g. with the nRF Connect app, then you need to log Bluetooth packets to see what is going on.

2022-07-13 18:12:41.390 21906-21935/org.test.tesi6 I/python: {0: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x9a2009e8>}

This shows there is only one characteristic with handle 0 which doesn’t seem right. I think we assumed that getInstanceId() returned the handle, but I think that it just returns a number that is unique per characteristics with the same UUID. That is, it returns 0 for most characteristics and 1 for the second instance of a characteristic with the same UUID as an already seen characteristic.

So, to fix this, I think we need to create a mapping between a tuple containing the UUID and instance id to a “handle” integer value to be compatible with other Bleak backends.