I've been looking all over the place for the required bluetooth connection parameters that will work for all three of these operating platforms. I'm using the HOGP (Bluetooth over HID GATT) profile for this project.
My project is an embedded system written by myself with a BLE module that I have control over the following parameters for connection.
Connection Interval Min
Connection Interval Max
Slave Latency
Supervision Timeout
Advertising Interval Min
Advertising Interval Max
My target devices to connect will be to satisfy connnections with Android >= 4.3, iOS7, and >= Win 8.1.
Apple was kind enough to give a document with the appropriate parameters on page 22 in the link below. I have not been able to find any information about Android and Win 8.
https://developer.apple.com/hardwaredrivers/bluetoothdesignguidelines.pdf
My current working settings for iOS7 tested fully with bidirection communication with freeware lightBlue is as follows. My embedded code and host software for iOS7 works.
Connection Interval Min 30ms
Connection Interval Max 56.25ms
Slave Latency 3
Supervision Timeout 5000ms
I've found from another stack overflow page that android allegedly works on 7.5ms Connection Interval from the following links.
Android BLE Connection time interval
http://processors.wiki.ti.com/index.php/Bluetooth_SensorTag?DCMP=lprf-stdroid&HQS=lprf-stdroid-pr-wiki1#Supported_Android_devices
Unfortunately the second requirement from apple iOS spec is that "Interval Min ≥ 20 ms".
Am I not understanding these ranges or how they are interpreted? If I set the Interval min to 7.5ms for Android wouldn't that void apples requirements? How can I satisfy both systems and also Win8 if possible?
My understanding is that the slave device offers a suggested setting in between the min and max and the master (smartphone) alerts the user of the actual selected value in that range.
I appreciated any help with this issue and hope this post could benefit others considering the fairly new and incomplete knowledge base for BLE.
Thanks in advance!
First, the connection interval defines a time window during which both devices use the same frequency to transfer data. There are 37 data channels in total, and connected devices hop through them every Connection Interval.
Thus, both devices has to agree on precise values for these parameters from the beginning in order to be in sync, i.e., connected.
Second, when connection is established the master (or Central) sends connection parameters it supports. The other device (or peripheral) just blindly takes them. iOS by default sets connection interval to 30 ms. After the connection is established the peripheral can request connection parameters update, by defining the min and max values, according to the guidelines apple has provide you with. The receiving part, read iOS in this case, will pick whatever it find best for it between [min;max], and will send back response with exact values it has picked. It also can reject the request, if the values do not comply with the guidelines.
Lastly, 7.5ms is the minimum length of the connection interval defined by Bluetooth specification. The maximum value is 4 s. The lower it is, the higher bandwidth, but higher power consumption. And the opposite in the higher values. The best value depends on the specific application. Considering that you work with HID profile I assume latency is important to you.
iOS says that it supports connection intervals down to 20ms (although I found it hard to achieve this some times), but in your case (HID profile) they also allow 11.25 ms.
Hope that helps.
To modify parameters in Android (requesting from Central to Peripheral) you can do something like this:
private String CONN_SERVICE_UUID = "00001800-0000-1000-8000-00805f9b34fb";
private static final UUID CONN_CHARACTERISTIC_UUID = UUID.fromString("00002a04-0000-1000-8000-00805F9B34FB");
private static final int CONN_INTERVAL = 0x0006;
private static final int SUPERVISION_TIMEOUT = 0x000A;
private void findServiceForConnectionParams(List<BluetoothGattService> gattServices){
BluetoothGattService connGattService = filterServices(gattServices, CONN_SERVICE_UUID);
if (connGattService != null) {
setConnectionInterval(connGattService);
}
}
private void setConnectionInterval(BluetoothGattService gattService) {
if (gattService == null) {
Log.e(TAG, "setConnectionInterval. Gatt service is null!");
return;
}
BluetoothGattCharacteristic connCharacteristic =
gattService.getCharacteristic(CONN_CHARACTERISTIC_UUID);
if (connCharacteristic != null) {
byte[] value = { (byte) (CONN_INTERVAL & 0x00FF), // gets LSB of 2 byte value
(byte) ((CONN_INTERVAL & 0xFF00) >> 8), // gets MSB of 2 byte value
(byte) (CONN_INTERVAL & 0x00FF),
(byte) ((CONN_INTERVAL & 0xFF00) >> 8),
0, 0,
(byte) (SUPERVISION_TIMEOUT & 0x00FF),
(byte) ((SUPERVISION_TIMEOUT & 0xFF00) >> 8)
};
connCharacteristic.setValue(value);
boolean status = mBluetoothGatt.writeCharacteristic(connCharacteristic);
Log.d(TAG, "setConnectionInterval. Change connection interval result: " + status);
} else {
Log.e(TAG, "setConnectionInterval. Connection characteristic is null!");
}
}
private BluetoothGattService filterServices(List<BluetoothGattService> gattServices, String targetUuid) {
for(BluetoothGattService gattService : gattServices){
String serviceUUID = gattService.getUuid().toString();
Log.i(TAG, "serviceUUID: " + serviceUUID);
if(serviceUUID.equals(targetUuid)){
Log.i(TAG, "serviceUUID matches! UUID: " + serviceUUID + " Type: " + gattService.getType());
// no needed, just to check which characteristics are offered
for(BluetoothGattCharacteristic characteristic : gattService.getCharacteristics()) {
Log.i(TAG, "serviceUUID characteristics: " + characteristic.getUuid().toString());
}
return gattService;
}
}
return null;
}
I must say though that it didn't work for me using Android 5 devices both as peripheral and central, because Generic Acces Service (0x1800) is not offering in my device Characteristic 0x2a04 for Preferred Connection Parameters. It's only offering 0x2a00 (device name) and 0x2a01 (appearance).
References:
http://www.cumulations.com/blogs/7/Doing-firmware-upgrade-over-BLE-in-Android
https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters.xml
https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.generic_access.xml
https://farwestab.wordpress.com/2011/02/05/some-tips-on-android-and-bluetooth/
I believe that this characteristic is only meant to provide information to the Central device. That is why it is generally read-only (for me, and cxphong). Bingen's answer does not universally work, and I am not certain it is meant to work that way. Has anybody actually got it to work on a specific device?
It appears that Android and iOS do not consult the information in this read-only characteristic, and so I am not certain that it is very useful.
What works for me is described below, for Cypress peripheral and Android central. A similar approach should work with other devices.
On peripheral, define the preferred connection parameters in a CYBLE_GAP_CONN_UPDATE_PARAM_T structure "params".
After GATT connection, in CYBLE_EVT_GATT_CONNECT_IND event handler (for example), call CyBle_L2capLeConnectionParamUpdateRequest(connHandle, ¶ms).
On the central side, there is nothing to do. After it receives the request, it will initiate the parameter update a bit later.
Cheers,
David
Related
Google introduced a set of limitations in Android 8 or 9 regarding Wi-Fi scanning frequency. Apps are restricted in how frequently they're able to scan for Wi-Fi connections, including P2P Wi-Fi peers. What is the situation with Wi-Fi Aware? Does it have the same limitation? Is it easier to bypass it?
This answer is as per the latest comments by OP.
One way to keep track of the RSSI of the network is to register for the intent RSSI_CHANGED_ACTION using a BroadcastReceiver and then extract the raw RSSI values from the Intent's extra values which are stored with the key Wi-FiManager.EXTRA_NEW_RSSI and obtain the threshold levels(usually the workable values) using calculateSignalLevel(). Some approximate code:
} else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
// Default to -200 as its below WifiManager.MIN_RSSI.
int rawRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
int rssiLevel = mWifiManager.calculateSignalLevel(rawRssi);
}
Also, to answer the previous question as to whether Wi-Fi aware is restricted by the same scan restrictions, the answer is 'no', not because it has a waiver vis-a-vis Wi-Fi-Direct but because it operates differently from a Wi-Fi-Direct connection. For a Wi-Fi Direct connection, one needs to make a request() to the WifiManager for initiating a scan and it is these scans that are throttled, with the duration of throttling varying based on whether the app is in foreground/background. This throttling can of course be overridden from the Developer Settings page.
Wi-Fi-Aware works with a different paradigm. If this is regarding the usage of ranging, then one can leverage Wi-Fi-Aware technology between two devices as follows:
Check whether ranging is supported using Wi-Fi-RTTI apis using context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);
Check whether Wi-Fi RTT is available by registering for the intent WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED and on its receipt, check for whether Wi-Fi RTT is available.
Create a ranging request
Start ranging
Extract rssi from a successful ranging result.
One thing to note is that the requests for ranging are limited to 20 from each UID as per this code from the framework.
static final int MAX_QUEUED_PER_UID = 20;
Note that if you're running as a regular application, your app would have its own UID.
My BLE server permanently measures a sensor value and sends a notification with 20 byte user data after each measurement. The goal is to generate as much throughput as possible.
On the client side, the value sent by the server is received and processed.
rxBleConnection.setupNotification(setDescriptorEnableNotification(characteristic))
.flatMap(notificationObservable -> notificationObservable)
.observeOn(Schedulers.newThread())
.buffer(1)
.subscribe(bytes -> {
onNotificationReceived(bytes, buffer);
} , throwable -> {
// Handle an error here.
onNotificationSetupFailure(throwable);
}
);
If I set the Connection intervall to 11.25ms, I receive all values. However, if I set the connection interval to 30ms, I receive a few values and then the connection is closed.
In the Android Log i see the followed message:
BleGattException status=8 (0x8),
bleGattOperationType=BleGattOperation{description='CONNECTION_STATE'
Why is the connection interrupted and what is the trigger?
With the help of a BLE Sniffer this is not recognizable. The set connection parameters are accepted and the transfer begins. Suddenly the transmission ends and the error message appears.
Update:
BLE Sniffer screenshot has been added:
30ms, this is connection interval you set in server or android?
Btw, on android you can set speed mode
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
}
Error 8 means the connection timed out. There is nothing wrong on the Android side. The problem is with the communication between the two Bluetooth controllers. If you have a sniffer then you should be able to see who is the one that fails to send packets.
Here is an Image from BLE Sniffer.
BLE Sniffer
I have the similar problem. On Android 7.0 there were two ways to keep connection:
1) If devices are bonded and there is a charachteristic reading callback with constant thread of packets. If there are no packets by some time, then connection fails.
2) If devides are not bonded, but I do TXCharacheristic.read every few seconds. If don't do that some time, then connection fails.
But now in Android 7.1.2 this way doesn't work.
May be the first way will work for you.
On your Android device you should make bonding, on your kit you should handle this bonding.
On Nexus 6P and Samsung S7 it doesn't work anymore, but I didn't try it on the other devices.
I suppose, you have min connection interval 7.5 on your BLE kit, but now it is deprecated on Android.
Try to set min connection interval to 11.25 on your BLE kit and set connection priority
gatt.requestConnectionPriority(CONNECTION_PRIORITY_HIGH);
where CONNECTION_PRIORITY_HIGH = 1
in onConnectionStateChange.
It had worked for me when I had changed min connection interval in my nordic from 7.5 to 11.25.
The Bluetooth Low Energy connection parameters management seems to have changed in Android 6.
I have a BLE Peripheral device who needs to use some specific connection parameters (notably, the connection interval), and I want to use the minimum connection interval allowed by the BLE specification (i.e. 7,5ms).
The Android SDK doesn't allow to choose it from the BLE GAP Central (the smartphone) side, so the proper way to do it is to make my GAP Peripheral device send a L2CAP Connection Parameter Update Request after the GAP connection is made.
The parameters I request are:
conn interval min : 7,5ms
conn interval max : 7,5ms
slave latency : 0
supervision timeout : 2000ms
This worked as expected with all Android devices I've been testing, from 4.3 to 5.x : after sending the L2CAP Connection Parameter Update Request, my device receives a L2CAP Connection Parameter Update Response with 0x0000 (accepted), followed by a LE Connection Update Complete event where I can see that the requested connection parameters have well been taken into account.
Now, with a Nexus 9 tablet or with 2 different Nexus 5 devices, all having Android 6.0.1, I can see that the the L2CAP Connection Parameter Update Request is always rejected (I receive a L2CAP Connection Parameter Update Response with 0x0001 (rejected)). Then I receive a LE Connection Update Complete event where I can see that the requested connection parameters have NOT been taken into account.
I've been trying this with 2 different implementations on the Peripheral side (one with ST Microelectronics' BlueNRG, one with Nordic Semiconductor's nRF52), both with the exact same result.
Then, after more testing : I have tried different parameter sets, changing the conn interval max (I kept other parameters the same). Here is what I found :
with conn interval max = 18.75ms, update request was accepted with interval set to 18.75ms
with conn interval max = 17.50ms, update request was accepted with interval set to 15.00ms
with conn interval max = 15.00ms, update request was accepted with interval set to 15.00ms
with conn interval max = 13.75ms, update request was accepted with interval set to 11.25ms
with conn interval max = 11.25ms, update request was accepted with interval set to 11.25ms
with any other conn interval max value below 11.25ms, I get rejected.
So the observation is that something has clearly changed with the way Android 6's BLE stack handles the connection parameters. But there doesn't seem to be any kind of information or documentation to confirm that.
My observations lead to a conclusion that the minimum connection interval allowed is now 11.25ms (which actually fits my needs) instead of 7.5ms in earlier Android versions. But having found it empirically, I'd want to be sure that I'm not missing some other constraints/rules or if that minimum would not be dynamic, depending for example on the current battery level...
What would be great would be to have the equivalent of Apple's Bluetooth Design Guidelines (cf. §3.6) to set things clear on how an LE Peripheral should deal with this topic.
Is anyone having the same issue or is aware of some more helpful information from Google ?
Compare method connectionParameterUpdate() from GattService.java in AOSP 6.0.1_r17 vs AOSP 5.1.1_r14. In both instances, call goes all the way to Buedroid in BTA_DmBleUpdateConnectionParams() in bta_dm_api.c with same params.
6.0:
switch (connectionPriority)
{
case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
minInterval = 9; // 11.25ms
maxInterval = 12; // 15ms
break;
case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
minInterval = 80; // 100ms
maxInterval = 100; // 125ms
latency = 2;
break;
}
5.1:
switch (connectionPriority)
{
case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
minInterval = 6; // 7.5ms
maxInterval = 8; // 10ms
break;
case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
minInterval = 80; // 100ms
maxInterval = 100; // 125ms
latency = 2;
break;
}
This might be a part of the answer to your question. Although BLE allows down to 7.5ms CI, I cannot speculate why link layer would not switch to lower CI on request by peripheral. I don't know if any part of android code controls outcome of negotiations with peripheral device.
Google has not provided any documentation about the Bluetooth LE stack changes concerning connection parameter changes even though there have clearly been some in Android 6.
My experience with it has been the same as your own, that being that 11.25ms is now the fastest connection interval allowed in Android 6+.
My educated guess as to why they don't release documentation is that many manufacturers put their own BLE stacks into their phones (the BLE on Samsung and HTC behave differently from vanilla Android).
One other observation I have made that caused a great deal of problems is that Android 6+ will change the connection parameters 2 to 6 times before settling on the requested parameters.
I observed that after requesting a connection parameter update interval of 800ms to 1100ms, I saw the initial interval come back at 7.5ms, that then jumped to 48.75ms and then jumped to the 1098.75ms I requested. Then I subscribed to notifications on one of my services and the connection interval again jumped back to 7.5ms and then back to 1098.75ms. After this, it stabilized at 1098.75ms for the duration of the connection.
These tests were run on a Nexus 6 with Android 6.0.1
Obviously, some very strange things are happening on the Android 6 BLE stack.
11.25 ms is the new minimum connection interval. The reason they don't allow 7.5 ms anymore is because if you stream audio over bluetooth at the same time the audio might became choppy.
Google guys made a mistake in one of recent commits in Bluedroid by defining BTM_BLE_CONN_INT_MIN_LIMIT as 0x0009 which gives you 1.25ms x 9 = 11.25ms. In order to comply with standard it has to be defined as 0x0006.
I am working on a project for configuring beacons. A certain amount of time after being powered on, a beacon becomes unconfigurable until it is power-cycled. In order to show a list of the configurable beacons, I am looking at certain characteristics (Bluetooth device name, certain manufacturer data in the advertising packet). I also need to know if it is "connectable", i. e. if the PDU Type in the BLE advertising packet for the device indicates that it is connectable. I've searched the Android Bluetooth classes high and low, both in Android 4.X and 5.X and haven't been able to find anything that will tell me this information.
I realize that one way to determine the beacon connectability is to connect up to it, e. g.: device.connectGatt(...). However, I've seen it take over two minutes sometimes before a callback to onConnectionStateChange comes back with STATE_DISCONNECTED. Also, there may be many of these beacons in an environment, and connecting up to every single one that might be configurable would be inefficient.
The iOS equivalent of this attribute can be found in the advertisementData dictionary under the key CBAdvertisementDataIsConnectable in the CBCentralManagerDelegate callback method centralManager:didDiscoverPeripheral:advertisementData:RSSI.
So, the question is: is there a way on Android to determine whether or not a BLE device is "connectable" from advertising data or scan result or ... ?
UPDATE: AS of the finalized APIs in the Android O SDK, the ScanResult class (itself added as of Android 5.0) now has the isConnectable() method. Detecting connectable advertisements is possible only on Android 8.0+. See here for more info: https://developer.android.com/reference/android/bluetooth/le/ScanResult.html#isConnectable()
Prior to Android 8.0, unfortunately it is not possible.
A connectable advertisement is determined by the PDU Header byte 0. You can see this in the example structure below:
d6 be 89 8e # Access address for advertising data (this is always the same fixed value)
40 # Advertising Channel PDU Header byte 0. Contains: (type = 0), (tx add = 1), (rx add = 0)
24 # Advertising Channel PDU Header byte 1. Contains: (length = total bytes of the advertising payload + 6 bytes for the BLE mac address.)
05 a2 17 6e 3d 71 # Bluetooth Mac
The problem is on devices prior to Anroid 8.0, the Android scanning APIs give you no access to these headers. You get exactly three fields in the callback from Android 4.x:
onLeScan(BluetoothDevice device, rssi, byte[] scan data)
The scan data byte array starts after the header bytes mentioned above. And from what I can see of the BluetoothDevice definition, none of the fields or methods tell you if it is a connectable advertisement -- the class is just a container for the bluetooth mac address with methods to exercise functions on the bluetooth stack. And there are no methods in IBluetooth.aidl which is the private interface to the bluetooth stack (and what BluetoothDevice calls to get its info) that can get this flag.
It appears that this information is not passed up to the Java layer from the BlueDroid stack prior to Android 8.0.
It should be possible since Nordic's nRF Master Control Panel does this.
After some digging I think I know how it does this. I'm not sure it's the right way to do it though.
I tried using the LE Advertiser and setting the device as connectable. In the Nordic app, a device is set as connectable depending on the bytes found at scanResult.getFlags().
I found that this code works for my devices:
int flags = scanResult.getScanRecord().getAdvertiseFlags();
if ((flags & 2) == 2) {
//connectable
}
I am implement the connection between Android and BLE? like Anti-lost or finder , After android phone has connected to the BLE device , phone read the RSSI of BLE device every second.
If the RSSI of BLE device is lower than RSSI threshold , it deem Out of Range. For example: Threshold is -70 , and the current RSSI of device is -80.
When app is deem Out of Range. it send the message to the BLE every 5 second. But it always disconnect after few times. I uses the following code to send the message to the BLE device.
BluetoothGattService HelloService = Gatt.getService(HELLO_SERVICE_UUID);
if(HelloService == null) {
Log.d(TAG, "HelloService not found!");
return;
}
//If the Service is not null , try to get the characteristic.
BluetoothGattCharacteristic Characteristic = HelloService.getCharacteristic(UUID_HELLO_CHARACTERISTIC);
if(Characteristic == null) {
Log.d(TAG, "Characteristic not found!");
return;
}
Gatt.setCharacteristicNotification(Characteristic, true);
Characteristic.setValue(text, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
Gatt.writeCharacteristic(Characteristic);
Log.d(TAG, "StepCount Characteristic End!");
The above code is correct , the BLE can receive the message. But the BLE device will disconnect after few second. It seems do more than one thing in a short time is burden to BLE device.
The question is: How to make the connection more stable between Android and BLE ?.
Some suggestions:
Don't use notifications if you can avoid it. Based on personal experience with some phones in some environments notifications can stop working and appear to cause general instability. Try to do periodic reads instead.
Only do a read or write once you have received a callback to BluetoothGattCallback.onCharacteristicWrite() or BluetoothGattCallback.onCharacteristicRead() for the previous read or write.
More generally, never do two things at once, whether that be scanning, connecting, reading, writing, whatever. You should serialize all operations using a job queue, only popping from that queue when the previous job completes (or fails).
In almost-out-of-range scenarios like you're talking about, operations can take a long time to complete, longer than 5 seconds sometimes. So doing another operation in 5 seconds or less you're effectively "stomping" the previous operation. However, operations can also never return with a callback in these cases, so you do have to implement a timeout. I use 10 seconds. Beyond that, the operation failed.