weliem / bluetooth-server-example

Example that shows how to implement a Bluetooth Peripheral on a phone
MIT License
6 stars 3 forks source link

No services in advertisement #3

Closed markmal closed 2 years ago

markmal commented 2 years ago

I've commented all Kotlin dependencies (not sure why it was there, there is no Kotlin code in the project) and managed to build the app for API 32. I run it in my Pixel 4. I can see the pixel device, but no services advertised.

Could you please take a look?

weliem commented 2 years ago

yes, the project needed some updates to make it work on Android 10.....

I updated the project and everything seems to work as far as I can tell.

Let me know if it works for you

markmal commented 2 years ago

Hi Weliem, I've re-cloned the project, it compiles without issues now. However the problem still there. in bluetoothctl (I am on Linux) I can see the device of my Android - "Pixel 4a". But there are no services. I tried another Time Server example project before. It works. But this one does not work though I see it calls bluetoothLeAdvertiser.startAdvertising(... and enters into BluetoothServer.onAdvertisingStarted(...) callback.

markmal commented 2 years ago

When I try to connect to the server it enters BluetoothServer.onCentralConnected() and then right away into BluetoothServer.onCentralDisconnected()

weliem commented 2 years ago

Please check it using apps like nrfConnect or LightBlue. When I run the example I definitely see an advertised service.

markmal commented 2 years ago

nrfConnect does not work in my Ubuntu. I see the Current Time Service advertised in LightBlue. I am trying to figure out ....

weliem commented 2 years ago

There is a difference in what is advertised and what it 'has' as services. The example app advertises the Heart Rate Service: "0000180D-0000-1000-8000-00805f9b34fb" But it also has the Device Information Service and Current Time service. But those are not in the advertisement. The max size of the advertisement is 31 bytes.

On Linux this is hard to distinguish because bluetoothctl doesn't show you the 'raw' advertisement. It only shows what it has learned about the device, either from the advertisement or from connecting to it and doing a full service discovery.

What you could do in bluetoothctl is to set a discovery filter on the HeartRate service UUID and then do a discovery. The device should then be found.

On Sat, Jul 9, 2022 at 11:13 PM Mark Malakanov @.***> wrote:

nrfConnect does not work in my Ubuntu. I see the Current Time Service advertised in LightBlue. I am trying to figure out ....

— Reply to this email directly, view it on GitHub https://github.com/weliem/bluetooth-server-example/issues/3#issuecomment-1179607498, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACT7HZZZAYV4UEYBGX3P5ALVTHTODANCNFSM53DUQEIA . You are receiving this because you commented.Message ID: @.***>

markmal commented 2 years ago

I've made Current Time service advertised. But linux does not see it advertised. When I connect it can see it. My watch (Nordic nRF52840 based) does not see advertised service too. When it connects it can subscribe for notifications. But cannot receive notifications. The Blessed issues 2 notifications then it stucks. This is weird. I've tried another code, made from this https://github.com/androidthings/sample-bluetooth-le-gattserver. It is simpler, it does not have queueing. But it works. My watch sees advertised service and receives notifications. I am completely puzzled. :(

weliem commented 2 years ago

Sounds weird....I use 2 Android phones and everything works totally fine.

try using 'sudo btmon' to monitor what is really going on on Linux. You can see the actual advertisement there....

weliem commented 2 years ago

This is what a raw advertisement looks like in btmon output:

> HCI Event: LE Meta Event (0x3e) plen 21                                                                #51 [hci0] 11.652482
      LE Advertising Report (0x02)
        Num reports: 1
        Event type: Connectable undirected - ADV_IND (0x00)
        Address type: Public (0x00)
        Address: C0:26:DA:13:38:FE (OUI C0-26-DA)
        Data length: 9
        Flags: 0x06
          LE General Discoverable Mode
          BR/EDR Not Supported
        16-bit Service UUIDs (partial): 2 entries
          Health Thermometer (0x1809)
          Device Information (0x180a)
        RSSI: -66 dBm (0xbe)
markmal commented 2 years ago

mine report shows:

HCI Event: LE Meta Event (0x3e) plen 28 #50 [hci0] 11.725181 LE Advertising Report (0x02) Num reports: 1 Event type: Connectable undirected - ADV_IND (0x00) Address type: Random (0x01) Address: 4B:62:4D:EE:DE:8B (Resolvable) Data length: 16 Flags: 0x02 LE General Discoverable Mode Name (complete): Pixel 4a MM RSSI: -74 dBm (0xb6)

HCI Event: LE Meta Event (0x3e) plen 19 #51 [hci0] 11.727175 LE Advertising Report (0x02) Num reports: 1 Event type: Scan response - SCAN_RSP (0x04) Address type: Random (0x01) Address: 4B:62:4D:EE:DE:8B (Resolvable) Data length: 7 TX power: -11 dBm 16-bit Service UUIDs (complete): 1 entry Current Time Service (0x1805) RSSI: -73 dBm (0xb7)

so it is 2 messages each time, 1-st ADV_IND without service, second SCAN_RSP with service.

markmal commented 2 years ago

when I run android example time server it shows:

HCI Event: LE Meta Event (0x3e) plen 32 #62 [hci0] 14.770011 LE Advertising Report (0x02) Num reports: 1 Event type: Connectable undirected - ADV_IND (0x00) Address type: Random (0x01) Address: 69:C0:11:E8:9A:28 (Resolvable) Data length: 20 Flags: 0x02 LE General Discoverable Mode Name (complete): Pixel 4a MM 16-bit Service UUIDs (complete): 1 entry Current Time Service (0x1805) RSSI: -77 dBm (0xb3)

HCI Event: LE Meta Event (0x3e) plen 12 #63 [hci0] 14.770995 LE Advertising Report (0x02) Num reports: 1 Event type: Scan response - SCAN_RSP (0x04) Address type: Random (0x01) Address: 69:C0:11:E8:9A:28 (Resolvable) Data length: 0 RSSI: -77 dBm (0xb3)

weliem commented 2 years ago

Ok, so that looks fine actually. Bluez combines the advertisement and scan response into 'one'.

I noticed in my code that I had actually swapped the advertisement and scanresponse. The correct order is: peripheralManager.startAdvertising(advertiseSettings, advertiseData, scanResponse);

... that at least explains why you see the Service UUID in the scan response rather than the advertisement. So if you swap those 2 parameters you will get the same as your other example.

markmal commented 2 years ago

Good catch! Now it shows service in ads. But now Name is in the SCAN_RSP.

HCI Event: LE Meta Event (0x3e) plen 22 #3 [hci0] 1.020584 LE Advertising Report (0x02) Num reports: 1 Event type: Connectable undirected - ADV_IND (0x00) Address type: Random (0x01) Address: 7F:CE:F3:4E:E3:09 (Resolvable) Data length: 10 Flags: 0x02 LE General Discoverable Mode TX power: -11 dBm 16-bit Service UUIDs (complete): 1 entry Current Time Service (0x1805) RSSI: -71 dBm (0xb9)

HCI Event: LE Meta Event (0x3e) plen 25 #4 [hci0] 1.022571 LE Advertising Report (0x02) Num reports: 1 Event type: Scan response - SCAN_RSP (0x04) Address type: Random (0x01) Address: 7F:CE:F3:4E:E3:09 (Resolvable) Data length: 13 Name (complete): Pixel 4a MM RSSI: -70 dBm (0xba)

weliem commented 2 years ago

Ok, mystery solved then?

markmal commented 2 years ago

Not completely. 1 - why now Name is in the SCAN_RSP ? 2 - my watch can subscribe for notifications but they do not come.

weliem commented 2 years ago

Regarding why it is in the scan response: because the code puts it there!

        AdvertiseData advertiseData = new AdvertiseData.Builder()
                .setIncludeTxPowerLevel(true)
                .addServiceUuid(new ParcelUuid(serviceUUID))
                .build();

        AdvertiseData scanResponse = new AdvertiseData.Builder()
                .setIncludeDeviceName(true)
                .build();

You can move it to the advertiseData if you want....

Regarding why your watch can subscribe for notifications but is not receiving them......I don't know. Looks like a problem with your watch. Have a look at the logs to learn more...

markmal commented 2 years ago

1. Yes! I've found how to add Name.

2. :) The watch works well with other programs including the original example code from Android developers.

markmal commented 2 years ago

it enters notifyCharacteristicChanged each second. But it enters into Runnable public void run() only once. However each time it returns true from notifyCharacteristicChanged, just because the notification is enqueued. Though the actual notification result should come bluetoothGattServer.notifyCharacteristicChanged that is inside Runnable.Run.

nextCommand() exits preliminary because commandQueueBusy == true.

` private boolean notifyCharacteristicChanged(@NotNull final byte[] value, @NotNull final BluetoothDevice bluetoothDevice, @NotNull final BluetoothGattCharacteristic characteristic) { Objects.requireNonNull(value, CHARACTERISTIC_VALUE_IS_NULL); Objects.requireNonNull(bluetoothDevice, DEVICE_IS_NULL); Objects.requireNonNull(characteristic, CHARACTERISTIC_IS_NULL); Objects.requireNonNull(characteristic.getValue(), CHARACTERISTIC_VALUE_IS_NULL);

    if (doesNotSupportNotifying(characteristic)) return false;

    final byte[] descriptorValue = characteristic.getDescriptor(CCC_DESCRIPTOR_UUID).getValue();
    final boolean confirm = supportsIndicate(characteristic) && Arrays.equals(descriptorValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
    return enqueue(new Runnable() {
        @SuppressLint("MissingPermission")
        @Override
        public void run() {
            currentNotifyValue = value;
            currentNotifyCharacteristic = characteristic;
            characteristic.setValue(value);
            if (!bluetoothGattServer.notifyCharacteristicChanged(bluetoothDevice, characteristic, confirm)) {
                Logger.e(TAG,"notifying characteristic changed failed for <%s>", characteristic.getUuid());
                BluetoothPeripheralManager.this.completedCommand();
            }
        }
    });
}

`

weliem commented 2 years ago

The current time service sends 'indications' which your watch must 'confirm'. The other example you are trying sends 'notifications' that don't have to be confirmed.

So my guess is that your watch doesn't confirm the indications and hence the queue gets stuck. I am using 2 Android phones and then is works perfectly because the confirmations are getting done.

markmal commented 2 years ago

Hmm. In the code it all named 'notifications'. Ok. I'll check the watch. If there any way to set up the Blessed to send 'notifications', not 'indications', just in case.

weliem commented 2 years ago

According to the GATT standard, the Current Time Service should send indications. But if you really want to change that to notifications, then modify this line:

private @NotNull final BluetoothGattCharacteristic currentTime = new BluetoothGattCharacteristic(CURRENT_TIME_CHARACTERISTIC_UUID, PROPERTY_READ | PROPERTY_INDICATE, PERMISSION_READ);

markmal commented 2 years ago

Yes! I've replaced PROPERTY_INDICATE with PROPERTY_NOTIFY and it works! I am looking into the watch code now. (It is open programmable watch Bangle.js 2)

weliem commented 2 years ago

100% watch problem then....

markmal commented 2 years ago

Actually it is not. I've fixed "my" program (that is derived from the Android devs program) by adding processing for ENABLE_INDICATION_VALUE in AdvertiseCallback.onDescriptorWriteRequest and it works!

    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
                                         BluetoothGattDescriptor descriptor,
                                         boolean preparedWrite, boolean responseNeeded,
                                         int offset, byte[] value) {
      if (TimeProfile.CLIENT_CONFIG.equals(descriptor.getUuid())) {
        if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
          Log.i(TAG, "Subscribe device to notifications: " + device);
          mRegisteredDevices.add(device);
        } else if (Arrays.equals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, value)) {
          Log.i(TAG, "Subscribe device to indications: " + device);
          mRegisteredDevices.add(device);
        } else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
          Log.i(TAG, "Unsubscribe device from notifications: " + device);
          mRegisteredDevices.remove(device);
        }

        if (Arrays.equals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, value)) {
          Log.i(TAG, "Subscribe device to notifications: " + device);
          mRegisteredDevices.add(device);
        } else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
          Log.i(TAG, "Unsubscribe device from notifications: " + device);
          mRegisteredDevices.remove(device);
        }

        if (responseNeeded) {
          mBluetoothGattServer.sendResponse(device,
            requestId,
            BluetoothGatt.GATT_SUCCESS,
            0,
            null);
        }
      } else {
        Log.w(TAG, "Unknown descriptor write request");
        if (responseNeeded) {
          mBluetoothGattServer.sendResponse(device,
            requestId,
            BluetoothGatt.GATT_FAILURE,
            0,
            null);
        }
      }
weliem commented 2 years ago

That doesn't make any difference, your still sending notifications...

markmal commented 2 years ago

no-no. I've of course replaced PROPERTY_NOTIFY with PROPERTY_INDICATE there.

weliem commented 2 years ago

No, that all doesn't matter...

The actual sending of a notification/indication is done by this call: https://developer.android.com/reference/android/bluetooth/BluetoothGattServer#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice,%20android.bluetooth.BluetoothGattCharacteristic,%20boolean)

...and you didn't change the code that makes that call. Hence you are still sending notifications.

markmal commented 2 years ago

I need to check the actual call, but first I've replaced PROPERTY_NOTIFY with PROPERTY_INDICATE and the program stopped working. Then I've added else if (Arrays.equals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, value)) { Log.i(TAG, "Subscribe device to indications: " + device); mRegisteredDevices.add(device); }

And it started working. But I will check the call.

markmal commented 2 years ago

You were right. The notifyCharacteristicChanged call had hardcoded confirmation=false . I've changed that to be True when ENABLE_INDICATION_VALUE received in onDescriptorWriteRequest. And the watch does not work now. I'll contact Bangle people...

weliem commented 2 years ago

Yep....it looks like the Bangle watch doesn't send the confirmations that are mandatory for indications.

markmal commented 2 years ago

Also, I've looked at GATT standard, Current Time should send notifications. https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=292957 3.1.2 Characteristic Behavior – Notification This characteristic can be configured for notification using the GATT Write Characteristic Descriptors sub-procedure on the Client Characteristic Configuration descriptor. ...

weliem commented 2 years ago

seems you are correct! ....but you will sooner or later encounter Indications when using other services.

markmal commented 2 years ago

Yes, I know. The Current Time is just a test service for me. Actually I am working on "Location and Navigation" one. THANKS for your help!

markmal commented 2 years ago

Question: I understand that notifyCharacteristicChanged returns True when a notification/indication sent successfully. How to capture a result of the indication confirmation (or absence of it)?

markmal commented 2 years ago

Hi Weliem, Do you know an answer to my question? Currently what happens - After connection established, Android calls notifyCharacteristicChanged and it returns True. The Watch receives the indication but does not respond with confirmation. BLE does not send next indications. However notifyCharacteristicChanged keeps returning True at each following call. How to detect this situation?

weliem commented 2 years ago

When a confirmation of an indication is received, Android will call onNotificationSent. In this library, I then complete the command and take the next in the queue. In your case, onNotificationSent isn't called and hence the queue is stuck.

markmal commented 2 years ago

What could be a strategy in such situation? Something like count issued indications and received confirmations. And after some difference reached, like 5 or 10 to disconnect the silent Central device?

weliem commented 2 years ago

In my opinion, not confirming indications means you are not implementing a basic mechanism of the Bluetooth spec. Not worth a workaround...

markmal commented 2 years ago

Hi Weliem, do you have an example how to process Write to a characteristic?

weliem commented 2 years ago

Not yet....I could expand the current time service with a 'write' option.

Basically:

weliem commented 2 years ago

updated the CurrentTimeService to support Write....