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 ... ?
(Android Developers Doc) This allows Android applications to communicate with BLE devices that have stricter power requirements, such as proximity sensors, heart rate monitors, fitness devices, home Automation System, Medical services and the Automotive industry.
To find BLE devices, you use the startScan () method. This method takes a ScanCallback as a parameter. You must implement this callback, because that is how scan results are returned.
To find BLE devices, you use the startScan () method. This method takes a ScanCallback as a parameter. You must implement this callback, because that is how scan results are returned. Because scanning is battery-intensive, you should observe the following guidelines: As soon as you find the desired device, stop scanning.
In the following example, the BLE app provides an activity ( DeviceScanActivity) to scan for available Bluetooth LE devices and display them in a list to the user. The following snippet shows how to start and stop a scan:
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With