Closed markmal closed 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
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.
When I try to connect to the server it enters BluetoothServer.onCentralConnected() and then right away into BluetoothServer.onCentralDisconnected()
Please check it using apps like nrfConnect or LightBlue. When I run the example I definitely see an advertised service.
nrfConnect does not work in my Ubuntu. I see the Current Time Service advertised in LightBlue. I am trying to figure out ....
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: @.***>
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. :(
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....
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)
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.
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)
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.
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)
Ok, mystery solved then?
Not completely. 1 - why now Name is in the SCAN_RSP ? 2 - my watch can subscribe for notifications but they do not come.
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...
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();
}
}
});
}
`
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.
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.
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);
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)
100% watch problem then....
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);
}
}
That doesn't make any difference, your still sending notifications...
no-no. I've of course replaced PROPERTY_NOTIFY with PROPERTY_INDICATE there.
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.
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.
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...
Yep....it looks like the Bangle watch doesn't send the confirmations that are mandatory for indications.
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. ...
seems you are correct! ....but you will sooner or later encounter Indications when using other services.
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!
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)?
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?
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.
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?
In my opinion, not confirming indications means you are not implementing a basic mechanism of the Bluetooth spec. Not worth a workaround...
Hi Weliem, do you have an example how to process Write to a characteristic?
Not yet....I could expand the current time service with a 'write' option.
Basically:
updated the CurrentTimeService to support Write....
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?