Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Descriptors showing on Android but not on iOS #168

Open
nimpy opened this issue Mar 31, 2023 · 2 comments
Open

Descriptors showing on Android but not on iOS #168

nimpy opened this issue Mar 31, 2023 · 2 comments

Comments

@nimpy
Copy link

nimpy commented Mar 31, 2023

Hi!

First, a disclaimer: I am not 100% sure this is an issue with bumble.

I have the following code (based on run_notifier.py), which creates a device with a service that has a characteristic that has a descriptor. These attributes (service, characteristic and descriptor) have custom UUIDs.

ble_device.py:

import asyncio
import os
import logging
import json

from bumble.device import Device, Connection
from bumble.transport import open_transport_or_link
from bumble.gatt import Service, Characteristic, Descriptor
from bumble.att import Attribute
from bumble.core import AdvertisingData

# -----------------------------------------------------------------------------
class Listener(Device.Listener, Connection.Listener):
    def __init__(self, device):
        self.device = device

    def on_connection(self, connection):
        print(f'=== Connected to {connection}')
        connection.listener = self

    def on_disconnection(self, reason):
        print(f'### Disconnected, reason={reason}')

    def on_characteristic_subscription(
        self, connection, characteristic, notify_enabled, indicate_enabled
    ):
        print(
            f'$$$ Characteristic subscription for handle {characteristic.handle} '
            f'from {connection}: '
            f'notify {"enabled" if notify_enabled else "disabled"}, '
            f'indicate {"enabled" if indicate_enabled else "disabled"}'
        )

# -----------------------------------------------------------------------------
async def main():

    print('<<< connecting to HCI...')
    # hardcode the device config and the serial port

    async with await open_transport_or_link('serial:/dev/tty.usbmodem1101') as (hci_source, hci_sink):
        print('<<< connected')

        # Create a device to manage the host
        device = Device.from_config_file_with_hci('brc1k/device1.json', hci_source, hci_sink)
        device.listener = Listener(device)

        descriptor = Descriptor("de42bd76-cfd2-11ed-afa1-0242ac120002",
                        Attribute.READABLE | Attribute.WRITEABLE,
                        )
        characteristic = Characteristic(
                    "de42bbbe-cfd2-11ed-afa1-0242ac120002",
                    Characteristic.READ |  Characteristic.NOTIFY,
                    Characteristic.READABLE | Characteristic.WRITEABLE,
                    bytes(json.dumps("no value here"), encoding='utf8'),
                    descriptors=[descriptor]
                    )

        service = Service("de42b830-cfd2-11ed-afa1-0242ac120002",
                           [characteristic]
                           )

        device.add_services([service])
        device.advertising_data = bytes(
            AdvertisingData(
                [
                    (
                        AdvertisingData.COMPLETE_LOCAL_NAME,
                        bytes('Bumble', 'utf-8'),
                    ),
                ]
            )
        )

        # Get things going
        await device.power_on()     
        await device.start_advertising(auto_restart=True)

        while True:
            await asyncio.sleep(3.0)


# -----------------------------------------------------------------------------
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
asyncio.run(main())

device1.json:

{
  "class_of_device": 2360322,
  "keystore": "JsonKeyStore",
  "advertising_interval": 100
}

On Android I can see this descriptor that I added, but on iOS it is not there. (In both cases, the CCCD descriptor is visible.)

Screenshot from nRF Connect (Android on the left, iOS on the right):
nRF_Connect_Descriptor

Also, when I use one of the pre-defined UUIDs for the descriptor (e.g. this one), it is again seen on Android but not on iOS.

One more thing, this should not be an issue with nRF Connect app, since I see this discrepancy between iOS and Android also using another (custom) app.

User @coalbr reported a similar issue on IOS-nRF-Connect repository.

So, my question is: does anyone have any idea why are these descriptors not showing on iOS and how to fix this? Any help would be very much appreciated!

@barbibulle
Copy link
Collaborator

It is possible that iOS does not expose custom descriptors to the nRF connect app (or other apps). I haven't checked, so this is just an idea. Have you been able to see custom descriptors on iOS when connected to other devices?
In any case, I'll run some experiments on my side to see if I can replicate and investigate this.

@nimpy
Copy link
Author

nimpy commented Apr 2, 2023

First of all, thanks for your willingness to look into this! :)

The only other Bluetooth device I have is my Garmin watch, but it doesn't have any custom descriptors (at least, they're not showing on Android).

Perhaps also interesting to mention, another discrepancy I found between iOS and Android: when I create a service with GATT_GENERIC_ATTRIBUTE_SERVICE UUID (same with some other ones: GATT_GENERIC_ACCESS_SERVICE, GATT_IMMEDIATE_ALERT_SERVICE), which has a custom characteristic, on Android I can see this characteristic, while on iOS I can't.

Below is the code for ble_device.py (device1.json is the same as in my message above):

import asyncio
import os
import logging
import json

from bumble.device import Device, Connection
from bumble.transport import open_transport_or_link
from bumble.gatt import Service, Characteristic
from bumble.gatt import GATT_GENERIC_ATTRIBUTE_SERVICE
from bumble.core import AdvertisingData

# -----------------------------------------------------------------------------
class Listener(Device.Listener, Connection.Listener):
    def __init__(self, device):
        self.device = device

    def on_connection(self, connection):
        print(f'=== Connected to {connection}')
        connection.listener = self

    def on_disconnection(self, reason):
        print(f'### Disconnected, reason={reason}')

    def on_characteristic_subscription(
        self, connection, characteristic, notify_enabled, indicate_enabled
    ):
        print(
            f'$$$ Characteristic subscription for handle {characteristic.handle} '
            f'from {connection}: '
            f'notify {"enabled" if notify_enabled else "disabled"}, '
            f'indicate {"enabled" if indicate_enabled else "disabled"}'
        )

# -----------------------------------------------------------------------------
async def main():

    print('<<< connecting to HCI...')
    # hardcode the device config and the serial port

    async with await open_transport_or_link('serial:/dev/tty.usbmodem1301') as (hci_source, hci_sink):
        print('<<< connected')

        # Create a device to manage the host
        device = Device.from_config_file_with_hci('brc1k/device1.json', hci_source, hci_sink)
        device.listener = Listener(device)

        characteristic = Characteristic(
                    "de42bbbe-cfd2-11ed-afa1-0242ac120002",
                    Characteristic.READ |  Characteristic.NOTIFY,
                    Characteristic.READABLE | Characteristic.WRITEABLE,
                    bytes(json.dumps("no value here"), encoding='utf8'),
                    )

        service = Service(GATT_GENERIC_ATTRIBUTE_SERVICE,
                          [characteristic])

        device.add_services([service])
        device.advertising_data = bytes(
            AdvertisingData(
                [
                    (
                        AdvertisingData.COMPLETE_LOCAL_NAME,
                        bytes('Bumble', 'utf-8'),
                    ),
                ]
            )
        )

        # Get things going
        await device.power_on() 
        await device.start_advertising(auto_restart=True)

        while True:
            await asyncio.sleep(3.0)


# -----------------------------------------------------------------------------
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
asyncio.run(main())

Screenshots from nRF Connect (Android on the left, iOS on the right):
nRF_Connect_Service

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants