Reputation: 4410
This is the advertiser (notice data
passed as AdvertiseData
type)
private void advertise() {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.setConnectable(false)
.build();
ParcelUuid pUuid = new ParcelUuid(UUID.fromString("cf2c82b6-6a06-403d-b7e6-13934e602664"));
AdvertiseData data = new AdvertiseData.Builder()
//.setIncludeDeviceName(true)
.addServiceUuid(pUuid)
.addServiceData(pUuid, "123456".getBytes(Charset.forName("UTF-8")))
.build();
AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(tag, "Advertising onStartSuccess");
super.onStartSuccess(settingsInEffect);
}
@Override
public void onStartFailure(int errorCode) {
Log.e(tag, "Advertising onStartFailure: " + errorCode);
super.onStartFailure(errorCode);
}
};
advertiser.startAdvertising(settings, data, advertiseCallback);
}
It starts succesfully.
This is the scanner
private void discover() {
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.build();
mBluetoothLeScanner.startScan(null, settings, mScanCallback);
}
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.i(tag, "Discovery onScanResult");
if (result == null) {
Log.i(tag, "no result");
return;
}
ScanRecord scanRecord = result.getScanRecord();
List<ParcelUuid> list = scanRecord != null ? scanRecord.getServiceUuids() : null;
if (list != null) {
Log.i(tag, scanRecord.toString());
for (ParcelUuid uuid : list) {
byte[] data = scanRecord.getServiceData(uuid);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.i(tag, "Discovery onBatchScanResults");
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(tag, "Discovery onScanFailed: " + errorCode);
}
};
In the callback onScarnResult
I log the scan record toString()
that produces this output
ScanRecord [mAdvertiseFlags=2,
mServiceUuids=[cf2c82b6-6a06-403d-b7e6-13934e602664],
mManufacturerSpecificData={},
mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]},
mTxPowerLevel=-2147483648, mDeviceName=null]
The uuid matches, unfortunately the result of
byte[] data = scanRecord.getServiceData(uuid)
is null
.
I noticed that the toString
output had the ASCII codes of the advertised data characters "123456", that are 49,50,51,52,53,54
mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]}
I'd like to receive the right advertised data, am I doing something wrong?
EDIT: the manifest has permissions for bluetooth, bt admin and location. The third one launches a request at runtime in Android 6
EDIT: by printing the whole scanRecord you get this output
ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[cf2c82b6-6a06-403d-b7e6-13934e602664], mManufacturerSpecificData={}, mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]}, mTxPowerLevel=-2147483648, mDeviceName=null]
Basically you can't use the uuid decided by the advertiser, which is in mServiceUuids array, because the key associated to mServiceData is another one. So I changed the code in this way, to navigate the data map and get the value (please, see the two if-blocks)
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result : results) {
ScanRecord scanRecord = result.getScanRecord();
List<ParcelUuid> uuids = scanRecord.getServiceUuids();
Map<ParcelUuid, byte[]> map = scanRecord.getServiceData();
if (uuids != null) {
for (ParcelUuid uuid : uuids) {
byte[] data = scanRecord.getServiceData(uuid);
Log.i(tag, uuid + " -> " + data + " contain " + map.containsKey(uuid));
}
}
if (map != null) {
Set<Map.Entry<ParcelUuid, byte[]>> set = map.entrySet();
Iterator<Map.Entry<ParcelUuid, byte[]>> iterator = set.iterator();
while (iterator.hasNext()) {
Log.i(tag, new String(iterator.next().getValue()));
}
}
}
}
In fact, the line
map.containsKey(uuid)
returns false because the uuid of the advertiser is not used by the data map.
I had to navigate the map to find the value (2nd if-block), but I don't have any means to know if that's the value I'm interested in. Either way I can't get the value if the system put another key that I can't know while running the scanner's code on the receiver app.
How can I handle this problem on the receiver? I'd like to use the data field, but the string key to get them is not known a priori and decided by the system.
Upvotes: 20
Views: 2864
Reputation: 1145
@guiv gave the right answer https://stackoverflow.com/a/54726135/1869562 here
Let me add by putting it in a more concise way.
addServiceData expects 16-bit UUIDS while addServiceUUIDs can accept either 16-bit or 128. Rather than raise an exception addServiceData automatically adjusts 128-bit UUIDs to 16-bit, hence the difference in input parameter of addServiceData (in Advertiser) and keys of the return value of getServiceData() (in Scanner).
Solution - Ensure you use 16-bit UUIDs in your advertiser
Upvotes: 0
Reputation: 31
I know it's an old thread, but since I had the same issue and found a solution...
UUIDs to be used with .addServiceUuid()
and .addServiceData()
in advertiser are different objects.
The first one identifies the service, and is a 128-bits UUID. The second one identifies the serviceData within that service, and is expected to be a 16-bits UUID.
This is why the scanner receives
0000**82b6**-0000-1000-8000-00805f9b34fb
Note that 16 bits 0x82b6
are common with the UUID passed to .addServiceData:
cf2c**82b6**-6a06-403d-b7e6-13934e602664
A 16-bits UUID is converted to a 128 bits by left-shifting of 96 bits and adding a Bluetooth constant UUID.
The solution is just to use an UUID of this form [0000xxxx-0000-1000-8000-00805f9b34fb
] to identify the serviceData in both advertiser and scanner. You can keep your orignal 128-bits UUID to identify the service.
Upvotes: 3
Reputation: 125
I found the error. Change this line:
.addServiceData(pUuid, "123456".getBytes(Charset.forName("UTF-8")))
to:
.addServiceData(pUuid, "123456".getBytes()
That's it. I used the same example code as you did.
Find a better example here:
https://github.com/googlesamples/android-BluetoothAdvertisements
Upvotes: 0
Reputation: 631
Try adding the ScanFilter and retreive the results
private void discover()
{
List<ScanFilter> filters = new ArrayList<ScanFilter>();
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid( new ParcelUuid(UUID.fromString(
getString(R.string.ble_uuid ) ) ) )
.build();
filters.add( filter );
Link for source code:https://github.com/tutsplus/Android-BluetoohLEAdvertising/blob/master/app/src/main/java/com/tutsplus/bleadvertising/MainActivity.java
Upvotes: 0